According to https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html each non-bounded type should be replaced by Object
by compiler.
I have a class:
public class Test<E> {
void setE(E e) {
return;
}
}
After decompilation in Idea the result is this:
public class Test<E> {
public Test() {
}
void setE(E e) {
}
}
Why is E
not replaced with Object
?
The tutorial is somewhat misleading here. It is trying to make a point about type erasure, but it doesn't do so very elegantly.
The bytecode for your generated class is as follows. Clearly this information is not discarded.
Last modified 2 Mar 2020; size 332 bytes
MD5 checksum e3b7faf33fd5666eae578bd516d0f903
Compiled from "Test.java"
public class Test<E extends java.lang.Object> extends java.lang.Object
minor version: 0
major version: 53
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // Test
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // Test
#3 = Class #17 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 setE
#9 = Utf8 (Ljava/lang/Object;)V
#10 = Utf8 Signature
#11 = Utf8 (TE;)V
#12 = Utf8 <E:Ljava/lang/Object;>Ljava/lang/Object;
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
#15 = NameAndType #4:#5 // "<init>":()V
#16 = Utf8 Test
#17 = Utf8 java/lang/Object
{
public Test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
void setE(E);
descriptor: (Ljava/lang/Object;)V
flags: (0x0000)
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 3: 0
Signature: #11 // (TE;)V
}
Signature: #12 // <E:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "Test.java"
The reason that this information cannot be thrown away in the bytecode is imagine that this class is part of a library; another project depends upon it. At compile-time in that 2nd project, their compiler needs to be able to make assertions about whether the generic type parameter is in bounds. If it had been thrown away completely, they'd have to write
Test test = new Test();
while I, in my source project, could write
Test<String> test = new Test<>():