true-generic.md 9.0 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1

沉默王二's avatar
泛型  
沉默王二 已提交
2

沉默王二's avatar
沉默王二 已提交
3 4
“二哥,为啥 Java 不能实现真正泛型啊?”三妹开门见山地问。

沉默王二's avatar
泛型  
沉默王二 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18
简单来回顾一下类型擦除,看下面这段代码。

```java
public class Cmower {
    public static void method(ArrayList<String> list) {
        System.out.println("Arraylist<String> list");
    }

    public static void method(ArrayList<Date> list) {
        System.out.println("Arraylist<Date> list");
    }
}
```

沉默王二's avatar
沉默王二 已提交
19 20 21
“三妹,你是不是认为 `ArrayList<String> list``ArrayList<Date> list` 是两种不同的类型,因为 String 和 Date 是不同的类。”我问。

“嗯。”三妹点点头。
沉默王二's avatar
泛型  
沉默王二 已提交
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

但由于类型擦除的原因,以上代码是不会编译通过的——编译器会提示一个错误:

>'method(ArrayList<String>)' clashes with 'method(ArrayList<Date>)'; both methods have same erasure

也就是说,两个 `method()` 方法经过类型擦除后的方法签名是完全相同的,Java 是不允许这样做的。

也就是说,按照我们的假设:如果 Java 能够实现真正意义上的泛型,两个 `method()` 方法是可以同时存在的,就好像方法重载一样。

```java
public class Cmower {
    public static void method(String list) {
    }

    public static void method(Date list) {
    }
}
```

沉默王二's avatar
沉默王二 已提交
41 42 43 44
“为什么 Java 不能实现真正意义上的泛型呢?背后的原因是什么?快告诉我呀!”三妹着急了。

“保持耐心,好不好?”我安慰道。

沉默王二's avatar
泛型  
沉默王二 已提交
45 46 47 48 49 50 51 52 53 54 55

第一,兼容性

Java 在 2004 年已经积累了较为丰富的生态,如果把现有的类修改为泛型类,需要让所有的用户重新修改源代码并且编译,这就会导致 Java 1.4 之前打下的江山可能会完全覆灭。

想象一下,你的代码原来运行的好好的,就因为 JDK 的升级,导致所有的源代码都无法编译通过并且无法运行,是不是会非常痛苦?

类型擦除就完美实现了兼容性,Java 1.5 之后的类可以使用泛型,而 Java 1.4 之前没有使用泛型的类也可以保留,并且不用做任何修改就能在新版本的 Java 虚拟机上运行。

老用户不受影响,新用户可以自由地选择使用泛型,可谓一举两得。

沉默王二's avatar
沉默王二 已提交
56
第二,不是“实现不了”。Pizza,1996 年的实验语言,在 Java 的基础上扩展了泛型。
沉默王二's avatar
泛型  
沉默王二 已提交
57 58 59

>Pizza 教程地址:http://pizzacompiler.sourceforge.net/doc/tutorial.html

沉默王二's avatar
沉默王二 已提交
60 61 62
“1996 年?”三妹表示很吃惊。

“嗯,你那会还没出生。”我说,“插一下 Java 的版本历史吧,你好有一个时间线上的观念。”
沉默王二's avatar
泛型  
沉默王二 已提交
63 64 65 66 67 68 69 70 71

- 1995年5月23日,Java语言诞生
- 1996年1月,JDK1.0 诞生
- 1997年2月18日,JDK1.1发布
- 1998年2月,JDK1.1被下载超过2,000,000次
- 2000年5月8日,JDK1.3发布
- 2000年5月29日,JDK1.4发布
- 2004年9月30日18:00 PM,J2SE1.5 发布

沉默王二's avatar
沉默王二 已提交
72
也就是说,Pizza 在 JDK 1.0 的版本上就实现了“真正意义上的”泛型,我引过来两段例子,你一看就明白了。
沉默王二's avatar
泛型  
沉默王二 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

首先是 StoreSomething,一个泛型类,标识符是大写字母 A 而不是我们熟悉的大写字母 T。

```java
class StoreSomething<A> {
     A something;

     StoreSomething(A something) {
         this.something = something;
     }

     void set(A something) {
         this.something = something;
     }

     A get() {
         return something;
     }
}
```

这个 A 呢,可以是任何合法的 Java 类型:

```java
StoreSomething<String> a = new StoreSomething("I'm a string!");
StoreSomething<int> b = new StoreSomething(17+4);

b.set(9);

int i = b.get();
String s = a.get();
```

对吧?这就是我们想要的“真正意义上的泛型”,A 不仅仅可以是引用类型 String,还可以是基本数据类型。要知道,Java 的泛型不允许是基本数据类型,只能是包装器类型。

沉默王二's avatar
沉默王二 已提交
108
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/generic/true-generic-01.png)
沉默王二's avatar
泛型  
沉默王二 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128

除此之外,Pizza 的泛型还可以直接使用 `new` 关键字进行声明,并且 Pizza 编译器会从构造方法的参数上推断出具体的对象类型,究竟是 String 还是 int。要知道,Java 的泛型因为类型擦除的原因,程序员是无法知道一个 ArrayList 究竟是 `ArrayList<String>` 还是 `ArrayList<Integer>` 的。

```java
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<String> strs = new ArrayList<String>();

System.out.println(ints.getClass());
System.out.println(strs.getClass());
```

输出结果:

```
class java.util.ArrayList
class java.util.ArrayList
```

都是 ArrayList 而已。

沉默王二's avatar
沉默王二 已提交
129
“那 Pizza 这种“真正意义上的泛型”为什么没有被 Java 采纳呢?”三妹很关心这个问题。
沉默王二's avatar
泛型  
沉默王二 已提交
130 131 132 133 134 135 136

事实上,Java 的核心开发组对 Pizza 的泛型设计非常感兴趣,并且与 Pizza 的设计者 Martin 和 Phil 取得了联系,新合作了一个项目 Generic Java,争取在 Java 中添加泛型支持,但不引入 Pizza 的其他功能,比如说函数式编程。

*这里再补充一点维基百科上的资料,Martin Odersky 是一名德国计算机科学家,他和其他人一起设计了 Scala 编程语言,以及 Generic Java(还有之前的 Pizza),他实现的 Generic Java 编译器成为了 Java 编译器 javac 的基础。*

站在马后炮的思维来看,Pizza 的泛型设计和函数式编程非常具有历史前瞻性。然而 Java 的核心开发组在当时似乎并不想把函数式编程引入到 Java 中。

沉默王二's avatar
沉默王二 已提交
137 138 139
以至于 Java 在 1.4 之前仍然是不支持泛型的。

“为什么 Java 1.5 的时候又突然支持泛型了呢?”三妹问。
沉默王二's avatar
泛型  
沉默王二 已提交
140

沉默王二's avatar
沉默王二 已提交
141
“当然是到了不支持不行的时候了。”我说。
沉默王二's avatar
泛型  
沉默王二 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

没有泛型之前,我们可以这样写代码:

```java
ArrayList list = new ArrayList();
list.add("沉默王二");
list.add(new Date());
```

不管是 String 类型,还是 Date 类型,都可以一股脑塞进 ArrayList 当中,这看起来似乎很方便,但取的时候就悲剧了。

```java
String s = list.get(1);
```

沉默王二's avatar
沉默王二 已提交
157
这样取行吗?不行。还得加上强制转换。
沉默王二's avatar
泛型  
沉默王二 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

```java
String s = (String) list.get(1);
```

但我们知道,这行代码在运行的时候必然会出错:

```
Exception in thread "main" java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String
```

这就又回到“兼容性”的问题了。

Java 语言和其他编程语言不一样,有着沉重的历史包袱,1.5 之前已经有大量的程序部署在生产环境下了,这时候如果一刀切,原来没有使用泛型的代码直接扼杀了,后果不堪想象。

Java 一直以来都强调兼容性,我认为这也是 Java 之所以能被广泛使用的主要原因之一,开发者不必担心 Java 版本升级的问题,一个在 JDK 1.4 上可以跑的代码,放在 JDK 1.5 上仍然可以跑。

*这里必须得说明一点,J2SE1.5 的发布,是 Java 语言发展史上的重要里程碑,为了表示该版本的重要性,J2SE1.5 也正式更名为 Java SE 5.0,往后去就是 Java SE 6.0,Java SE 7.0。。。。*

但 Java 并不支持高版本 JDK 编译生成的字节码文件在低版本的 JRE(Java 运行时环境)上跑。

沉默王二's avatar
沉默王二 已提交
179
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/generic/true-generic-02.png)
沉默王二's avatar
泛型  
沉默王二 已提交
180 181


沉默王二's avatar
沉默王二 已提交
182
针对泛型,兼容性具体表现在什么地方呢?来看下面这段代码。
沉默王二's avatar
泛型  
沉默王二 已提交
183 184 185 186 187 188 189 190 191

```java
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<String> strs = new ArrayList<String>();
ArrayList list;
list = ints;
list = strs;
```

沉默王二's avatar
沉默王二 已提交
192
“兼容性表现在上面这段代码必须得能够编译运行。怎么办呢?”我扭头看了一下旁边的三妹,继续说。
沉默王二's avatar
泛型  
沉默王二 已提交
193

沉默王二's avatar
沉默王二 已提交
194
“只能搞类型擦除了!”我妹肯定地回答。
沉默王二's avatar
泛型  
沉默王二 已提交
195

沉默王二's avatar
沉默王二 已提交
196
“是滴。”
沉默王二's avatar
泛型  
沉默王二 已提交
197 198 199 200 201 202 203 204 205 206 207

编译前进行泛型检测,`ArrayList<Integer>` 只能放 Integer,`ArrayList<String>` 只能放 String,取的时候就不用担心类型强转出错了。

但编译后的字节码文件里,是没有泛型的,放的都是 Object。

Java 神奇就神奇在这,表面上万物皆对象,但为了性能上的考量,又存在 int、double 这种原始类型,但原始类型又没办法和 Object 兼容,于是我们就只能写 `ArrayList<Integer>` 这样很占用内存空间的代码。

这恐怕也是 Java 泛型被吐槽的原因之一了。

一个好消息是 Valhalla 项目正在努力解决这些因为泛型擦除带来的历史遗留问题。

沉默王二's avatar
沉默王二 已提交
208
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/generic/true-generic-03.png)
沉默王二's avatar
泛型  
沉默王二 已提交
209 210 211 212 213 214 215 216


Project Valhalla:正在进行当中的 OpenJDK 项目,计划给未来的 Java 添加改进的泛型支持。

>源码地址:http://openjdk.java.net/projects/valhalla/

让我们拭目以待吧!

沉默王二's avatar
沉默王二 已提交
217 218
“怎么样?三妹,这下全部都明白了吧!”我问。

沉默王二's avatar
更新  
沉默王二 已提交
219 220 221 222
“嗯嗯。二哥,你讲得可真棒👍”三妹夸奖得我有点小开心,嘿嘿。

-----

沉默王二's avatar
优化  
沉默王二 已提交
223
**Java 程序员进阶之路**》预计一个月左右会有一次内容更新和完善,大家在我的公众号 **沉默王二** 后台回复“**03**” 即可获取最新版!如果觉得内容不错的话,欢迎转发分享!
沉默王二's avatar
更新  
沉默王二 已提交
224

沉默王二's avatar
沉默王二 已提交
225
<img src="https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/itwanger.png" alt="图片没显示的话,可以微信搜索「沉默王二」关注" style="zoom:50%;" />