什么?Java泛型是假的?

299 阅读1分钟

1、问题背景

今天和新来的同事小孙讨论到限制容器中数据类型的问题,小孙一脸的不屑,这还不简单,加个泛型不就可以了!于是他就用以下代码向我演示了一遍:

public class GenericityTest {
    public static void main(String[] args) {
        ArrayList<Integer> intArr = new ArrayList<>();
        intArr.add(1);
​
        /*
         * Required type: Integer
         * Provided: String
         */
        //intArr.add("abc"); // 添加失败
​
        // 打印 intArr
        for (int i = 0; i < intArr.size(); i++) {
            System.out.println(intArr.get(i));
        }
    }
}

2、反驳说明

看起来好像是这么一回事,可是这怎么能行呢?我必须要让他知道社会的残酷。

我找到小孙:“你可以帮我看看这段代码吗?为什么我加了泛型,可是把字符串也给加进去了?”。

public class GenericityMain {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        ArrayList<Integer> intArr = new ArrayList<>();
        intArr.add(1);
​
        Class<? extends ArrayList> arrClazz = intArr.getClass();
        Method arrAddMethod = arrClazz.getMethod("add", Object.class);
        // Method arrAddMethod = arrClazz.getDeclaredMethod("add", Object.class);
        arrAddMethod.invoke(intArr, "abc");
​
        // 打印 intArr
        for (int i = 0; i < intArr.size(); i++) {
            System.out.println(intArr.get(i));  // 1 abc
        }
    }
}

小孙:“这,这,这......”。

3、原因说明

在使用泛型时,我们要知道因为什么引入了泛型?

Java泛型是在 JDK5 中引入的,他提供了编译时类型安全检测机制。由这我们就知道了,泛型只是在编译时起作用,那就简单了,我们可以通过一些手段跳过编译期不就可以了吗?

4、JVM说明

找到类 GenericityTest的编译文件 GenericityTest.class,我们来反汇编看看 JVM 编码是怎么样的?

$ javap -c GenericityTest

警告: 二进制文件GenericityTest包含com.hz.genericity.GenericityTest
Compiled from "GenericityTest.java"
public class com.hz.genericity.GenericityTest {
  public com.hz.genericity.GenericityTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       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: iconst_1
      10: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      13: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      16: pop
      17: iconst_0
      18: istore_2
      19: iload_2
      20: aload_1
      21: invokevirtual #6                  // Method java/util/ArrayList.size:()I
      24: if_icmpge     
      27: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      30: aload_1
      31: iload_2
      32: invokevirtual #8                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
      35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      38: iinc          2, 1
      41: goto          19
      44: return
}

说明:我们主要看 13 行 #5 add:(Ljava/lang/Object)这里我们看到,在JVM中还是Object,并不是我们认为的 Integer。