I noticed a slightly counterintuitive Java language specification, so I'll make a note of it. The verification environment is OpenJDK 11 (build 28) for Windows.
public static <T> void hoge(@NonNull T value){
Class<? extends T> type = value.getClass(); //Can't compile
//I want to work with type
}
Since value should be T
or its subclass, it seems to be okay at first glance, but this code cannot be compiled.
javac
GenericGetClass.java:3:error:Incompatible type: Class<CAP#1>Class<? extends T>Cannot be converted to:
Class<? extends T> type = value.getClass();
^
If T is a type variable:
Method<T>hoge(T)T extends Object declared in
CAP#If 1 is a new type variable:
?CAP from capture of extends Object#1 extends Object
1 error
Somehow I don't understand the error message ...
I'll try some.
//Of course OK
public static void hoge(CharSequence value){
Class<? extends CharSequence> type = value.getClass();
}
//This is also OK
public static <T extends CharSequence> void fuga(T value){
Class<? extends CharSequence> type = value.getClass();
}
//This cannot be compiled
public static <T extends CharSequence> void piyo(T value){
Class<? extends T> type = value.getClass();
}
It seems that an error will occur if the type variable T
is used for the type to be assigned. After trying so far, I remembered the story that the handling of the type of ʻObject # getClass ()` is special, so I checked it again.
getClass ()
According to the API Specifications
The actual result type is Class<? extends |X|>is. here,|X|Is the elimination of the static type of the expression for which getClass is called.
In the first place, the ʻObject class is defined as
public final Class <?> GetClass () `, so
CharSequence cs = ...;
Class<?> type = cs.getClass()
Is possible, but if you treat it like a normal method
Class<? extends CharSequence> type = cs.getClass()
It will not be possible to substitute like. Therefore, it is treated specially by using the language, and in this case, the type of getClass ()
is regarded as Class <? Extends Char Sequence>
. Specifically, the type limit is determined by the type of the variable at compile time (= "static type").
The problem here is that it is "erasing static type". "Erase" seems to be a subtle translation, but Language specification §4.6 It's probably the erasure of. It seems to indicate List
forList <String>
, CharSequence
for T
when class Hoge <T extends CharSequence>
, and so on.
public static <T extends CharSequence> void fuga(T value){
Class<? extends CharSequence> type = value.getClass(); // OK
}
In the case of, since value
is of type T
and its ʻextends specification [^ 1] is
CharSequence, the eraser of
T is
CharSequence, which is the return value of
getClass (). The type is
? Extends CharSequence. This isn't necessarily
T or its subclasses, so you can't assign it to
Class <? Extends T> `.
[^ 1]: It is leftmost bound in English, but is there any good Japanese translation?
In the first example
public static <T> void hoge(@NonNull T value){
Class<? extends T> type = value.getClass(); //Can't compile
}
The eraser for T
is ʻObject, so the type for
getClass () is
? Extends Object`.
From here, the author's guess is quite included, but the reason why the eraser is specified instead of the generic type in the first place is that this happens with the generic type.
ArrayList<String> list = new ArrayList<>();
Class<? extends ArrayList<String>> type = list.getClass(); //Actually a compile error
I have a bad feeling.
Instances of the Class
class do not have detailed type information due to the mechanism [^ 2], so objects of theClass <ArrayList <String >>
type andClass <ArrayList <Integer >>
type are actually It will be an instance of the same Class <ArrayList>
. The method of Class <ArrayList>
cannot know what the type variable T
of ʻArrayList
That is, variables of type Class <ArrayList <String >>
are dangerous. If the above example can be compiled,
[^ 2]: Since there is a unique Class object for each class (so you can compare with ==
)
ArrayList<Integer> another = new ArrayList<>();
another.add(123);
ArrayList<String> casted = type.cast(another); //At the time of execution, it is just treated as a cast to ArrayList, so it passes
// ...
String str = casted.get(0);//Run-time error here?
It's likely that you'll get a ClassCastException
in an unexpected location, even though you've written and compiled it without using raw types overall.
I understand why you can't use generic types. That said, I thought I wouldn't have to delete the type variable, but it's the same if the type variable T
is assigned toList <String>
on the caller. Therefore, it seems that programs like the one at the beginning are not allowed.
I noticed this behavior
@SuppressWarnings("unchecked")
public static <K extends Enum<K>> EnumMap<K,String> of(@NonNull K key, String value){
Class<K> type = (Class<K>) key.getClass(); //Compile error if not cast
EnumMap<K,String> map = new EnumMap<>(type); //Passing an extended Enum constant will result in a runtime error here
map.put(key, value);
return map;
}
It was when I was writing code like [^ 4]. In this case, I thought it was okay because ʻenumis
final, but if you think about it carefully, it was out because it would be a different class if you define a method for each constant of ʻenum
.
[^ 4]: Actually, I was doing something like an original implementation of EnumMap.
I wrote an article about Qiita for the first time, but if you think about it and write it, it will certainly help you organize your knowledge. Please point out any points that cannot be reached.
Recommended Posts