类型擦除 checkcast 反射赋值泛型集合运行时表现
类型擦除
类型擦除是在编译做了校验, 到字节码都是obj, 可以通过反射绕过。
那么在运行时, 通过反射设置的obj能否被正常使用呢?
Talk is cheap. Show me the code.
Code Test case 1
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by guoxd on 2020/7/21.
*/
public class Test {
public static void main(String[] args)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List list = new ArrayList();
list.add(1);
list.getClass().getMethod("add", Object.class).invoke(list, "a");
list.getClass().getMethod("add", Object.class).invoke(list, new A());
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
Object obj = list.get(2);
System.out.println(obj.getClass());
Integer integer = list.get(1);
System.out.println(integer);
}
public static class A{
int a = 1;
@Override
public String toString() {
return String.valueOf(a);
}
}
}
通过这个小试验,可以发现
- 反射可以放任何类型的obj到list里
- 直接println是可以正常输出的,可以猜到输出应该是调用了
Object.toString
的方法 - 企图用Integer去接get的值, 会报异常
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Code Test case 2
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Created by guoxd on 2020/7/21.
*/
public class Test {
public static void main(String[] args)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List list = new ArrayList();
list.getClass().getMethod("add", Object.class).invoke(list, "a");
// part a 直接get的情况,外层方法需要Object参数
System.out.println(list.get(0));
Objects.equals(list.get(0), null);
// part b 使用Object接, 在对obj调用方法
Object obj = list.get(0);
System.out.println(obj.getClass());
// part c 演示强转的字节码
String a = (String) obj;
// part d get后直接调用方法
System.out.println(list.get(0).getClass());
System.out.println(list.get(0).toString());
// part e get后先强转Obj, 再调用方法
System.out.println(((Object) list.get(0)).getClass());
}
}
通过javac
和javap -v
获取编译后的字节码, 结合字节码来分析
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class;
12: ldc #5 // String add
14: iconst_1
15: anewarray #6 // class java/lang/Class
18: dup
19: iconst_0
20: ldc #7 // class java/lang/Object
22: aastore
23: invokevirtual #8 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
26: aload_1
27: iconst_1
28: anewarray #7 // class java/lang/Object
31: dup
32: iconst_0
33: ldc #9 // String a
35: aastore
36: invokevirtual #10 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
39: pop
40: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_1
44: iconst_0
45: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
50: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
53: aload_1
54: iconst_0
55: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
60: aconst_null
61: invokestatic #14 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
64: pop
65: aload_1
66: iconst_0
67: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
72: astore_2
73: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
76: aload_2
77: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class;
80: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
83: aload_2
84: checkcast #15 // class java/lang/String
87: astore_3
88: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
91: aload_1
92: iconst_0
93: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
98: checkcast #16 // class java/lang/Integer
101: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class;
104: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
107: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
110: aload_1
111: iconst_0
112: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
117: checkcast #16 // class java/lang/Integer
120: invokevirtual #17 // Method java/lang/Integer.toString:()Ljava/lang/String;
123: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
126: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
129: aload_1
130: iconst_0
131: invokeinterface #12, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
136: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class;
139: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
142: return
分段来看字节码
-
part a 对应字节码第一行对应40-50,第二行对应53-64
- 可以看到get到的对象是Object,然后println、equals等外层方法需要Object参数,因此不需要类型转换,直接执行
-
part b 对应字节码前两行对应65-80
- get到的对象直接赋值给obj,后续对obj做操作,不需要类型转换
-
part c 对应字节码83-87
- 这里是为了展示,字节码是怎么实现一次强转的,看字节码可以发现,只是做了一次checkcast,后续详细介绍下checkcast
-
part d 对应字节码第一行对应88-104,第二行对应107-123
- get后直接调用方法,可以发现在对对象做操作前,已经进行了checkcast的判断,不同于part a的测试, 这里是直接调用了对象的方法, 而不是将对象的引用传到其他方法中做参数。 这两行代码因为有checkcast, 都会报
ClassCastException
- get后直接调用方法,可以发现在对对象做操作前,已经进行了checkcast的判断,不同于part a的测试, 这里是直接调用了对象的方法, 而不是将对象的引用传到其他方法中做参数。 这两行代码因为有checkcast, 都会报
-
part e 对应字节码126-139
- get后先强转Obj, 再调用方法, 发现这样字节码里就没有checkcast的判断了,不会报错
checkcast
通过上述实验,发现错误都是因为编译后的字节码包含checkcast, 导致在运行时报错。 那么checkcast到底做了什么呢?
checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type.
更详细信息可以查看
结论
- 运行时,反射放到集合里的对象,会在checkcast时触发ClassCastException
- get对象后,使用Object来引用、作为Object类型的参数传到方法中,都不会触发checkcast
- get对象后,使用泛型类型来引用、直接调用对象的方法(不管是继承自object还是泛型类本身的方法), 都会触发checkcast的检查