Java getClass () is incompatible with generics (or type variables)

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.

What you can't do

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 ...

What's wrong?

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.

Specifications for 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.

In this example?

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`.

Why is it such a specification?

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 ` is set to.

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.

Digression

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 ʻenumisfinal, 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.

Afterword

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

Java getClass () is incompatible with generics (or type variables)
What is thread safe (with Java)
[Personal memo] Java data type is annoying
Internal class type: With or without static
[Java] Introducing the Generics Boundary Wildcard Type
About the meaning of type variables, E, T, etc. used in generics used in Java
[Java] Generics
The intersection type introduced in Java 10 is amazing (?)
Use Matplotlib from Java or Scala with Matplotlib4j
What is the volatile modifier for Java variables?
[Read Effective Java] Chapter 2 Item 3 "Force singleton characteristics with private constructor or enum type"