ZB-036-01泛型的由来和泛型擦除

没有泛型之前实现类型安全

  • 利用组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Main {

// 只能存放字符串 的 list
public static class StringList{
public List list = new ArrayList();

public void add(String s){
list.add(s);
}

public String get(int i){
return (String) list.get(i);
}

public int size(){
return list.size();
}
}

// 只能存放整数 的 list
public static class IntList{
public List list = new ArrayList();

public void add(Integer s){
list.add(s);
}

public Integer get(int i){
return (int) list.get(i);
}

public int size(){
return list.size();
}
}

// 从 String 到 Object 的映射
public static class StringObjectMap{
Map map = new HashMap<>();

public void put(String key,Object value){
map.put(key,value);
}

public Object get(String key){
return map.get(key);
}

}

public static void main(String[] args) {
StringList stringList = new StringList();
stringList.add("a");
stringList.add("b");

IntList intList = new IntList();
intList.add(1);
intList.add(2);

StringObjectMap map = new StringObjectMap();
map.put("name","xxx");
map.put("age",18);
}

}

java1.5之后泛型出现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {


ArrayList<String> stringList2 = new ArrayList<>();
stringList.add("a");
stringList.add("b");

ArrayList<Integer> intList2 = new ArrayList<>();
intList2.add(1);
intList2.add(2);

HashMap<String,Object> map2 = new HashMap<>();
map.put("name","xxx");
map.put("age",18);

}

}

此时你再也不用写之前麻烦的代码

看一看泛型类 ArrayList 的定义

1
2
3
4
5
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
}

ArrayList<E> 里的E是什么

  • 一个泛型有一个类型声明(声明在类上) ArrayList<E> E
  • 可以通过传递一个参数进去,把这个类型变成全新的类型
    1
    2
    3
    4
    5
    ArrayList<String>
    ArrayList<Integer>
    ArrayList<Cat>
    ArrayList<Dog>
    // 你传递什么类型 它就变成什么类型

问题来了

1
2
3
4
5
6
7
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();

list1.add(1); // 报错 只能添加 String 类型
list2.add("1"); // 报错 只能添加 Integer 类型
// 报错 不能相互赋值
ArrayList<Integer> list1 = new ArrayList<String>();
  • 它俩的区别,它们还是同一个类吗?

答案是:它们是同一个类,也不是同一个类

它是同一个类,因为有 擦除

它不是同一个类,因为不能相互赋值

泛型好处

  • 类型安全
  • 方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HashMap hashMap = new HashMap();
// 它的 key是 String, value 也是 String
hashMap.put("name","abc");
hashMap.put("age","18");

for (Object key: hashMap.keySet()) {
String stringKey = (String)key;
String stringValue = (String)hashMap.get(key);
}

// 如果你用的是泛型就不需要这样转换类型
HashMap<String,String> hashMap2 = new HashMap();
// 它的 key是 String, value 也是 String
hashMap2.put("name","abc");
hashMap2.put("age","18");

for (String key: hashMap2.keySet()) {
String key2 = key;
String value2 = hashMap2.get(key);
}

因此有了泛型

  • 从此,我们可以省力的方法编写类型安全的代码
  • List
  • Map<String,Object>
  • Map<Stirng,List>

从无泛型的世界1.4到有泛型的世界1.5,那我们还要支持无泛型的“原始类型”的容器吗?

答案是:我们不想支持,但是不得不支持!

因为向后兼容。这样的好处是 JDK1.0可以无缝在最新的JDK上运行 而且已经过了20多年了

反观 Python2 / Python3 它为了更好的语法特性或改正一些缺陷放弃了向后兼容性,而java为了向后兼容性放弃了更好地语法特性

因为向后兼容性带来的问题

1
2
3
4
5
6
List list = new ArrayList();
list.add(new Object());
list.add(1);
list.add("123");
// 假设穿越到95年
// list 可以添加任何类型没有限制
  • java向后兼容性
  • 为了兼容性,只有两个选择
    • 1擦除 java的选择
    • 2搞一套全新的API,C#的选择

为什么选择擦除

1
2
3
4
5
6
7
JDK1.0的时候就存在的容器 
它们是被废弃的容器
Vector
HashTable
JDK1.2已经分裂了一次

JDK1.5 泛型的时候 如果在分裂 不是不行就是有点丑

怎么实现擦除的

Main.java

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {

String[] arr = new String[2];
ArrayList<String> list = new ArrayList<>();


}
}

编译后 Main.class

1
2
3
4
5
6
7
8
9
public class Main {
public Main() {
}

public static void main(String[] args) {
String[] arr = new String[2];
new ArrayList();
}
}

此时查看它的字节码文件 (IDEA顶部工具栏view -> ShowByte code)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// class version 52.0 (52)
// access flags 0x21
public class com/loder/demo/Main {

// compiled from: Main.java

// access flags 0x1
public <init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/loder/demo/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1

// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 9 L0
ICONST_2
ANEWARRAY java/lang/String
ASTORE 1
L1
LINENUMBER 10 L1
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 2
L2
LINENUMBER 12 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE arr [Ljava/lang/String; L1 L3 1
LOCALVARIABLE list Ljava/util/ArrayList; L2 L3 2
// signature Ljava/util/ArrayList<Ljava/lang/String;>;
// declaration: list extends java.util.ArrayList<java.lang.String>
MAXSTACK = 2
MAXLOCALS = 3
}

// 你会惊讶的看到 ArrayList<String>上的 String 怎么没了

为什么说java的泛型是一个假泛型

  • 因为java的运行只看字节码,而字节码里ArrayList没有泛型信息 String (它被擦除了)

这就是为什么说java是假泛型的原因

因此称这个过程为 类型擦除

擦除带来的问题

  • Java的泛型是编译器的泛型
    • 泛型信息在运行期完全不保留
      1
      2
      3
      4
      5
      6
      7
      ArrayList<String> list1 = new ArrayList<>();
      list1.add(1); // 编译器报错 提示你不能添加 不是 String的类型到 list1

      List list = new ArrayList();
      list.add(new Object()); // 此时编译器会给你警告 说你声明的list没有泛型
      list.add(1);
      list.add("123");

编译器的警告

使用限定符List<?>

1
2
3
4
5
6
7
8
9
10
11
12
13
List list = new ArrayList(); 
list.add(new Object());
list.add(1);
list.add("123");

//你可以这样
List<?> list = new ArrayList();
// 此时编译器标红
list.add(new Object());

//你可以 alt + enter 代码就变成了这样
List<Object> list = new ArrayList();
list.add(new Object());

利用类型擦除绕过编译器检查

List<String>并不是 List<Object>的子类型

  • 类比String/Object,String[]/Object[]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {

static class Animal{}

static class Cat extends Animal{}

public static void foo(Animal a){}

public static void foo(Animal[] a){}

public static void foo(List<Animal> l){}

public static void main(String[] args) {
// 可以传递子类型进入
foo(new Animal());
foo(new Cat());

// 即使是数组也可以传递 子类型数组
foo(new Animal[2]);
foo(new Cat[2]);

foo(new ArrayList<Animal>());
foo(new ArrayList<Cat>()); // 报错 此时竟然不能传递子类型的容器
}

}

原因是:类型擦除你实际传递的是 new ArrayList()

另一个问题

1
2
3
4
5
6
7
public static void foo(List<Animal> l){}

public static void foo(List l){}

// 看上去不一样,但是会报错,因为泛型是有抹去的(擦除)
实际它们都是
public static void foo(List l){}

绕过编译器检查

1
2
3
4
5
6
7
8
9
10
ArrayList<Animal> list = new ArrayList<>();

// 你可以这样
ArrayList rawList = list;
rawList.add("");
rawList.add(1);
rawList.add(new Object());

// 还可以这样
ArrayList<Cat> catArrayList = (ArrayList) list;

数组是真泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) {

String[] s = new String[2];
// 通过了检查
testStringArray(s);
}
// 数组是真泛型,即使你通过了编译期的类型检查,但是运行还是会报错

public static void testStringArray(Object[] o){
// 这里修改为 int 类型
o[0] = 1;
}

}

// 运行报错
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer

但是如果是 List 就不会报错

  • 意思是只要通过编译器检查,就万事大吉了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {

public static void main(String[] args) {

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

public static void testListSafety(List<Object> o){
// 这里添加 int 类型
o.add(1);
}

}

// 不报错

结论

所谓的java泛型是编译期的,运行时就完全没了