Java泛型。为什么它能编译通过?

13
abstract class Type<K extends Number> {
    abstract <K> void use1(Type<K> k);           // Compiler error (Type parameter K is not within its bounds)
    abstract <K> void use2(Type<? extends K> k); // fine
    abstract <K> void use3(Type<? super K> k);   // fine
}

方法的泛型类型K覆盖了类的泛型类型K,因此在use1()中,<K><K extends Number>不匹配。编译器对于use2()use3()中的新泛型类型<K>一无所知,但仍然可以合法编译。为什么<? extends K>(或者<? super K>)与<K extends Number>匹配?

4个回答

9
您面临的问题是有两种不同的K类型。如果将其中一种重新命名,可能会更清晰易懂。
abstract class Type<N extends Number> {
    abstract <K extends Number> void use1(Type<K> k); // fine
    abstract <K> void use2(Type<? extends K> k); // fine
    abstract <K> void use3(Type<? super K> k);   // fine
}

有时您需要提供编译器可以推断的重复信息,而其他情况则不需要。在Java 7中,它添加了一个<>钻石符号来告诉编译器推断之前未推断的类型。


为了说明我的意思,这里有不同的方式来创建泛型类的实例。有些需要给出类型两次,有些只需要一次。编译器可以推断类型。

一般情况下,在大多数其他语言可能会推断类型的情况下,Java不会推断类型。

class Type<N extends Number> {
    private final Class<N> nClass;

    Type(Class<N> nClass) {
        this.nClass = nClass;
    }

    static <N extends Number> Type<N> create(Class<N> nClass) {
        return new Type<N>(nClass);
    }

    static void main(String... args) {
      // N type is required.
      Type<Integer> t1 = new Type<Integer>(Integer.class);

      // N type inferred in Java 7.
      Type<Integer> t2 = new Type<>(Integer.class); 

      // type is optional
      Type<Integer> t3 = Type.<Integer>create(Integer.class); 

      // type is inferred
      Type<Integer> t4 = create(Integer.class);
    }

在你的示例中,为什么定义N却不使用它? - Fred
2
@Fred,原始问题也没有使用类声明中定义的K。 - Gandalf
我不明白这个回答如何解决问题。问题是为什么use2和use3可以编译,而你似乎根本没有涉及到这一点。 - sepp2k
@sepp2k,希望我现在已经做到了。 - Peter Lawrey
2
@Peter:我不明白在哪里。在原帖的代码中,use1没有编译成功,因为它的K没有扩展Number。在你的代码中,你通过添加“extends Number”来修复了这个问题。然而,问题不是为什么use1没有编译成功,而是为什么use2和use3可以编译成功,即使它们的K也没有扩展Number。也就是说,当K不扩展Number时,“Type<? extends K>”为什么是合法的,而“Type<K>”不是?我不明白你的回答解决了这个问题。 - sepp2k
@sepp2k,简而言之,编译器可以并且确实推断一些类型。然而,它在何时这样做或不这样做,并不总是表现出一致性(已经给出了例子,并说明了在Java 7中发生了变化)。 - Peter Lawrey

5
当您像这样定义方法时:

def 方法名(参数):

abstract <K> void use1(Type<K> k);

您在类定义中有效地隐藏了类型K。您应该能够像这样定义方法:

abstract void use1(Type<K> k);

4

首先,为了避免阴影,让我们重写它:

abstract class Type<N extends Number> {
    abstract <K> void use1(Type<K> k); 
    abstract <K> void use2(Type<? extends K> k); 
    abstract <K> void use3(Type<? super K> k);   
}

在第一种方法中,K 作为 Type<N extends Number> 的类型参数,因此它的值应符合 TypeN 的限制。然而,方法声明对 K 的值没有任何限制,因此这是不合法的。如果您在 K 上添加必要的限制,则可以使其合法:
abstract <K extends Number> void use1(Type<K> k);

在以下方法中,Type 的实际类型参数是未知的(?),而 K 对其施加了额外的限制,因此这些声明中没有任何非法行为。
下面是一个更实际的例子,其中包含类似的声明:
class MyList<N extends Number> extends ArrayList<N> {}

<K> void add1(MyList<K> a, K b) {
     a.add(b); // Given the method declaration, this line is legal, but it 
          // violates type safety, since object of an arbitrary type K can be
          // added to a list that expects Numbers
          // Thus, declaration of this method is illegal
}

<K> void add2(MyList<? extends K> a, K b) {
     // a.add(b) would be illegal inside this method, so that there is no way
     // to violate type safety here, therefore declaration of this method is legal
}

<K> void add3(MyLisy<? super K> a, K b) {
     a.add(b); // This line is legal, but it cannot violate type safey, since
          // you cannot pass a list that doesn't expect K into this method
}

0

这是一个灰色地带;javac 7和6有分歧;JLS3已经过时;不知道新规范在哪里。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接