haxianhe's blog

破壁Java - 泛型

字数统计: 1.2k阅读时长: 4 min
2021/09/11 Share

什么是泛型

  • 泛型就是编写模板代码来适应任意类型;
  • 泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
  • 注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。

泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>:

1
ArrayList<String> strList = new ArrayList<String>();

由编译器针对类型作检查:

1
2
3
4
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

在Java标准库中的ArrayList实现了List接口,它可以向上转型为List

1
2
3
4
5
public class ArrayList<T> implements List<T> {
...
}

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

即类型ArrayList可以向上转型为List

要特别注意:不能把ArrayList向上转型为ArrayList或List

使用泛型

使用泛型时,把泛型参数替换为需要的class类型,例如:ArrayList,ArrayList等;

可以省略编译器能自动推断出的类型,例如:List list = new ArrayList<>();;

不指定泛型参数类型时,编译器会给出警告,且只能将视为Object类型;

可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。

编写泛型

编写泛型类比普通类要复杂。通常来说,泛型类一般用在集合类中,例如ArrayList,我们很少需要编写泛型类。

如果我们确实需要编写一个泛型类,那么,应该如何编写它?

可以按照以下步骤来编写一个泛型类。

首先,按照某种类型,例如:String,来编写类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}

然后,,把特定类型String替换为T,并申明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
  • 编写泛型时,需要定义泛型类型

  • 静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法;

  • 泛型可以同时定义多种类型,例如Map<K, V>。

擦拭法

泛型是一种类似”模板代码“的技术,不同语言的泛型实现方式不一定相同。

Java语言的泛型实现方式是擦拭法(Type Erasure)。

所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

例如,我们编写了一个泛型类Pair,这是编译器看到的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}

而虚拟机根本不知道泛型。这是虚拟机执行的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}

因此,Java使用擦拭法实现泛型,导致了:

  • 编译器把类型视为Object;
  • 编译器根据实现安全的强制转型。

使用泛型的时候,我们编写的代码也是编译器看到的代码:

1
2
3
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();

而虚拟机执行的代码并没有泛型:

1
2
3
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();

所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

了解了Java泛型的实现方式——擦拭法,我们就知道了Java泛型的局限,擦拭法决定了泛型

  • 不能是基本类型,例如:int;
  • 不能获取带泛型类型的Class,例如:Pair.class;
  • 不能判断带泛型类型的类型,例如:x instanceof Pair
  • 不能实例化T类型,例如:new T()。

泛型方法要防止重复定义方法,例如:public boolean equals(T obj);

子类可以获取父类的泛型类型




CATALOG
  1. 1. 什么是泛型
  2. 2. 使用泛型
  3. 3. 编写泛型
  4. 4. 擦拭法