乐趣区

关于java:类型擦除-checkcast-反射赋值泛型集合运行时表现

类型擦除 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());
    }
}

通过 javacjavap -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
  • 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 的查看
退出移动版