共计 5204 个字符,预计需要花费 14 分钟才能阅读完成。
类型擦除 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<Integer> 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<Integer> 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."<init>":()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 的查看
正文完