If you create a generic class in Java (the class has generic type parameters), can you use generic methods (the method takes generic type parameters)?
Consider the f
'for backwards compatibility' seems a sufficient reason for the type erasure of class generic types - it is needed e.g. to allow you to return an untyped List and pass it to some legacy code. The extension of this to generic methods seems like a tricky sub-case.
The JLS snippet from 4.8 (which you quote) covers constructors, instance methods and member fields - generic methods are just a particular case of instance methods in general. So it seems your case is covered by this snippet.
Adapting JLS 4.8 to this specific case :
The type of a generic method is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.
(here the 'type' of the method would include all parameter and return types). If you interpret 'erasure' as 'erasing all generics', then this does seem to match the observed behaviour, although it is not very intuitive or even useful. It almost seems like an overzealous consistency, to erase all generics, rather than just generic class parameters (although who am I to second guess the designers).
Perhaps there could be problems where the class generic parameters interact with the method generic parameters - in your code they are fully independent, but you could imagine other cases where they are assigned / mixed together. I think it's worth pointing out that use of raw types are not recommended, as per the JLS :
The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types
Some of the thinking of the java developers is apparent here :
http://bugs.sun.com/view_bug.do?bug_id=6400189
(bug + fix showing that a method's return type is treated as part of the method's type for the purposes of this type erasure)
There is also this request, where someone appears to request the behaviour you describe - only erase the class generic parameters, not other generics - but it was rejected with this reasoning:
The request is to modify type erasure so that in the type declaration
Foo<T>
, erasure only removesT
from parameterized types. Then, it so happens that withinMap<K,V>
's declaration,Set<Map.Entry<K,V>>
erases toSet<Map.Entry>
.But if
Map<K,V>
had a method that took typeMap<String,V>
, its erasure would just beMap<String>
. For type erasure to change the number of type parameters is horrific, especially for compile-time method resolution. We are absolutely not going to accept this request.It is too much to expect to be able to use raw types (
Map
) while still getting some of the type-safety of generics (Set<Map.Entry>
).
With Java Generics, if you use the raw form of a generic class, then all generics on the class, even unrelated generic methods such as your makeSingletonList
and doSomething
methods, become raw. The reason as I understand it is to provide backwards compatibility with Java code written pre-generics.
If there is no use for your generic type parameter T
, then simply remove it from MyGenericClass
, leaving your methods generic with K
. Else you'll have to live with the fact that the class type parameter T
must be given to your class to use generics on anything else in the class.
From my comment on MAnyKeys answer:
I think the full type erasure makes sense combined with the mentioned backwards compatability. When the object is created without generic type arguments it can be (or shoudl be?) assumed that the usage of the object takes place in non generic code.
Consider this legacy code:
public class MyGenericClass {
public Object doSomething(Object k){
return k;
}
}
called like this:
MyGenericClass foo = new MyGenricClass();
NotKType notKType = foo.doSomething(new NotKType());
Now the class and it's method are made generic:
public class MyGenericClass<T> {
public <K extends KType> K doSomething(K k){
return k;
}
}
Now the above caller code wouldn't compile anymore as NotKType
is not a suptype of KType
. To avoid this the generic types are replaced with Object
. Although there are cases where it wouldn't make any difference (like your example), it is at least very complex for the compiler to analyze when. Might even be impossible.
I know this szenario seems a little constructed but I'm sure it happens from time to time.
This doesn't answer the fundamental question, but does address the issue of why makeSingletonList(...)
compiles whilst doSomething(...)
does not:
In an untyped implementation of MyGenericClass
:
MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String");
...is equivalent to:
MyGenericClass untyped = new MyGenericClass();
List list = untyped.makeSingletonList("String");
List<String> typedList = list;
This will compile (with several warnings), but is then open to runtime errors.
Indeed, this is also analogous to:
MyGenericClass untyped = new MyGenericClass();
List<Integer> list = untyped.makeSingletonList("String"); // this compiles!!!
Integer a = list.get(0);
...which compiles but will throw a runtime ClassCastException
when you try and get the String
value out and cast it to an Integer
.
I've found one reason to fully discard generics (however it's not quite good). Reason is: generics may be bounded. Consider this class:
public static class MyGenericClass<T> {
public <K extends T> K doSomething(K k){
return k;
}
public <K> List<K> makeSingletonList(K k){
return Collections.singletonList(k);
}
}
The compiler has to discard generics in doSomething
, when you use the class without generics.
And I think all generics are discarded to be consistent with this behavior.
makeSingletonList
compiles because Java does an unchecked cast from List
to List<K>
(however compiler displays warning).
The reason for this is backwards compatibility with pre-generics code. The pre-generics code did not use generic arguments, instead using what seems today to be a raw type. The pre-generics code would use Object
references instead of references using the generic type, and raw types use type Object
for all generic arguments, so the code was indeed backwards compatible.
As an example, consider this code:
List list = new ArrayList();
This is pre-generic code, and once generics were introduced, this was interpreted as a raw generic type equivalent to:
List<?> list = new ArrayList<>();
Because the ? doesn't have an extends
or super
keyword after it, it is transformed into this:
List<Object> list = new ArrayList<>();
The version of List
that was used before generics used an Object
to refer to a list element, and this version also uses an Object
to refer to a list element, so backwards compatibility is retained.