通俗易懂的讲解:Java 泛型优点、原理、限制和实现

第一时间获取 IT 技术干货!

阅读文本大概需要 6 分钟。

1

类 型 擦 除

Java是怎么实现泛型的?不错,类型擦除。Java编译器将源码编译成字节码的时候会将你在源码中声明的类型进行擦除,比如:

List<String> list = new ArrayList<String>();

在编译后,字节码中只有List,运行在JVM中也是一样的,那你可能会有疑问,既然将类型擦除了,那为什么我声明的泛型为String类型时,不能往里add一个整型的数据呢?这是因为编译器在编译前会进行类型检查,类型不一致会直接编译报错。
一般作为初级工程师知道这些就算合格了。

我们往深一层研究下,难道我们一定不能往声明泛型为String的list中增加一个整型元素吗?聪明的同学可能想到了,既然是在编译前检查类型的,编译后又将类型擦除了,那我是不是可以在运行时通过反射将整型数字add进去?不错!确实是可以的。

List<Long> list = new ArrayList<Long>();
list.add(100L);
list.getClass().getMethod("add", Object.class).invoke(list, "abc");


// 然后我们将list打印出来
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

打印出来确实是可以的,但是我能改成下面这样吗?

List<Long> list = new ArrayList<Long>();
list.add(100L);
list.getClass().getMethod("add", Object.class).invoke(list, "abc");


Long a = 0L;
// 然后我们将list打印出来
for (int i = 0; i < list.size(); i++) {
    a += list.get(i);
}

当然不能啦,当然如果你想的比较仔细,会发现一个很好玩的现象。对于上面这个list中第二个元素为"abc",那我们分别以不同的类型来赋值:

String s = "";
Long a = 0L;
s = list.get(1);
a = list.get(1);

无论你是用String还是Long类型来接收都会报错,你用String来接收会报"Long cannot be converted to String"。你用Long类型来赋值会报"String cannot be cast to java.lang.Long",你是不是感到迷茫了,其实这一切都是因为类型擦除,对于不能使用String类型来接收是因为编译器会做检查,因为list声明的泛型是Long类型的,而你使用String类型来赋值显然编译器会报错,第二种你使用Long类型来接收,编译器当然会认为是合法的,但是在运行的时候,list中的第二个实际值是String,强转成Long当然会报错了。这一切的根源是你使用反射向list中放入了一个和声明不同类型的数据。正常我们一般也不会这么做啦,这里只是验证一下这个机制而已。

好了,解释了这么多类型擦除的机制,那Java使用类型擦除来实现泛型有什么好处呢?


1、第一点我们将如此多的泛型在编译时擦除了,那么在运行时显然可以省不少的内存空间嘛。

2、第二点不得不说下兼容性,Java是在1.5版本推出的泛型,那1.5之前存在大量的线上代码没有泛型的,总不能舍弃吧,所以编译擦除后和没有泛型不是一样吗,这就兼容了之前更老的Java版本。

如果到这里你基本上都会的话,我觉得完全具有中级工程师的能力了。

2

类 型 擦 除 带 来 的 问 题

任何设计都会有自己的优点和缺点,在了解类型擦除的优点之后,我们也要剖析下类型擦除存在的现实问题:

1、不能使用基本数据类型

对于基本数据类型我们必须使用它的装箱类,那在我们使用过程中必然会平凡的涉及到拆箱和装箱的操作,这必定带来一定的资源开销,所以谷歌在针对key是int类型的情况下,使用SparseArray来代替HashMap。

2、不能用来方法的重载

为什么呢?举个例子:

如上图所示,在不同的泛型作为参数时,编译器编译时进行类型擦除,那参数不就一样了吗?那还谈什么重载呢!而C#没有进行类型擦除,所以编译完后是带有泛型的类型的,所以可以当作是重载的。

3、泛型类型不能当作真实的类型使用

上图中展示了5种使用方式,除了第四种Java能正常使用,其他Java都不能使用,而C#完全没问题。

4、静态方法无法引用类的泛型类型

Java中的泛型是类实例化的时候才能确定泛型的准确类型,而静态方法是不需要类实例化就能调用的,显然不能使用。

5、类型强转的开销

在Java1.5之前的版本,如上图所示,必须要进行强转才能使用自己想要的类型。
那Java1.5及以后的版本呢?

有兴趣的可以看看ArrayList的源码,它的get方法还是会做强转的。

如果到这里你都知道,说明你对泛型以及类型擦除了解的还是比较系统,完全具备高工的潜质。

推荐阅读:

千万别告诉别人,这是我从高工那偷听来的Java方法分派策略

你说你是高工,匿名内部类有我玩得6吗?

你说你是高工,String有多长也不知道?

你说你是高工,char都没搞明白?!

THANDKS

- End -

猜你喜欢

转载自blog.csdn.net/codeyanbao/article/details/103545255