Java Generics. Why does it compile?
abstract class Type<K extends Number> {
abstract <K> void use1(Type<K&g开发者_JAVA技巧t; 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
}
The method generic type K shadows the class generic type K, so <K>
doesn't match <K extends Number>
in use1()
.The compiler doesn't know anything usefull about new generic type <K>
in use2()
and use3()
but it is still legal to compile . Why <? extends K>
(or <? super K>
) match <K extends Number>
?
The problem you have is that There are two K
types. It may be clearer if you rename one.
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
}
There are cases where you have to provide duplicate information the compiler can infer, and other places where you don't. In Java 7 it has added a <>
diamond notation to tell the compiler to infer types it didn't previously.
To illustrate what I mean. Here is different ways to create an instance of a generic class. Some requires the type be given twice, others only once. The compiler can infer the type.
In general, Java doesn't infer types when it might do in most other languages.
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);
}
When you define the method like this:
abstract <K> void use1(Type<K> k);
You're effectively hiding the type K
in your class definition. You should be able to define the methods like this:
abstract void use1(Type<K> k);
First of all, let's rewrite it to avoid shadowing:
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);
}
In the first method K
acts as a type parameter of Type<N extends Number>
, thus its value sould comply to the bound of Type
's N
. However, method declaration doesn't have any restrictions on value of K
, therefore it's not legal. It would be legal if you add a necessary restriction on K
:
abstract <K extends Number> void use1(Type<K> k);
In the following methods, the actual type parameter of Type
is unknown (?
), and K
imposes additional bound on it, so that there is nothing illegal in these declarations.
Here is a more practical example with the similar declarations:
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
}
This is a gray area; javac 7 and 6 disagree; JLS3 is outdated; no idea where's the new spec.
精彩评论