关于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的查看

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理