类型擦除 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的查看