Java漫游笔记-07-泛型

为什么需要泛型

JDK 1.5 引入泛型。
在这之前,经常会看到这样的代码:

1
2
3
ArrayList nameList = new ArrayList(); // 不知道这个是什么类型的集合
nameList.add("Franky"); // 可以向数组列表中添加任意类型的对象
String name = (String) nameList.get(0); // 必须进行强制转换

从代码中,我们不难发现问题:

  • 当我们向数组列表中添加元素时,没有任何的类型安全检查。换言之,我们可以往列表中添加任意类型的对象。
  • 当我们从数组列表中获取一个值时,必须进行强制类型转换。

而有了泛型之后:

1
2
3
ArrayList<String> nameList = new ArrayList<String>(); // 显式声明集合中包含的是 String 对象
nameList.add("Franky"); // 只能添加 String 对象,否则编译不通过
String name = nameList.get(0); // 不需要进行类型转换

泛型的好处是显而易见的:

  • 代码具有更好的可读性,能够清楚的知道类型。(例如:ArrayList<String> 就可以这么理解:这是一个 String 类型的数组列表。)
  • 在编译期提供类型安全检查。
  • 所有的类型转换都是自动完成的。

使用泛型的注意事项

  • 不能创建泛型对象数组,例如:

    1
    Pair<String, String>[] pairs = new Pair<String, String>[10]; // 错误的用法
  • 不能实例化泛型变量,例如:

    1
    2
    3
    4
    public class ClassName<T> {
    new T(); // 错误的用法
    T obj; // 定义泛型变量,OK
    }

    或者是:

    1
    2
    T.class.newInstance(); // 错误的用法
    t.getClass().newInstance(); // 通过泛型变量反射构造是可以的
  • 静态泛型变量也是不行的,例如:

    1
    public static T instance; // 错误的用法
  • 假设 A extends B 成立,但是,C<A> extends C<B> 并不成立。
    对于我们编写代码而言,需要注意的是:

    1
    2
    3
    List<B> list = new ArrayList<A>(); // 错误的用法
    List<? extends B> list = new ArrayList<A>(); // OK
    List<? super A> list = new ArrayList<B>(); // OK
  • 通配符不是类型变量,这样用也是不行的:

    1
    ? val = getVal(); // 错误的用法

实例

下面的代码演示了如何定义泛型类,泛型方法:

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
66
67
/**
* 泛型类
*/

public class Pair<F, S> {

private F first;
private S second;

public Pair() {
first = null;
second = null;
}

public Pair(F first, S second) {
this.first = first;
this.second = second;
}

public F getFirst() {
return first;
}

public void setFirst(F first) {
this.first = first;
}

public S getSecond() {
return second;
}

public void setSecond(S second) {
this.second = second;
}

/**
* 泛型方法
*/

public static <T> T getThird(T third) {
return third;
}

/**
* 带限制的泛型方法
*/

public static <T extends Number & Comparable<T>> T getFourth(T fourth) {
return fourth;
}

/**
* 实例化
*/

public static <F, S> Pair<F, S> newPair(Class<F> cf, Class<S> cs) {
try {
return new Pair<F, S>(cf.newInstance(), cs.newInstance());
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
return null;
}
}

@Override
public String toString() {
return "Pair [first=" + first + ", second=" + second + "]";
}

}

测试代码:

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
import org.junit.Test;

public class PairTest {

@Test
public void testGenericClass() {
Pair<String, Integer> p1 = new Pair<String, Integer>();
p1.setFirst("one");
p1.setSecond(1);
System.out.println(p1);

Pair<String, String> p2 = new Pair<String, String>("two", "two");
System.out.println(p2);

@SuppressWarnings("rawtypes")
// 相当于 Pair<Object, Object>
Pair p3 = new Pair();
System.out.println(p3);
}

@Test
public void testGetThird() {
// 相当于 Integer value1 = Pair.<Integer>getThird(3);
Integer value1 = Pair.getThird(3);
System.out.println("Third is " + value1);

String value2 = Pair.getThird("three");
System.out.println("Third is " + value2);
}

@Test
public void testGetFourth() {
Integer value1 = Pair.getFourth(4);
System.out.println("Fourth is " + value1);
}

@Test
public void testGetPair() {
Pair<String, String> pair = Pair.newPair(String.class, String.class);
System.out.println(pair);
}

}

总结

泛型的本质是类型的参数化
这意味着我们编写的代码可以被很多不同类型的对象所重用。
泛型在使用方式类似于 C++ 中的模板。然而, Java 中的泛型是伪泛型,可以看作是一种“语法糖”,也就是编译器实现的一个小把戏。
因为,Java 中的泛型只存在于源代码,在编译的过程中,泛型会被替换成真实的类型(这个过程也叫做“类型擦除”)。
这种实现方式,也会给我们带来一定影响,例如,在重载方法的时候,下面的代码有一些 JDK 是无法编译通过的:

1
2
3
4
5
6
7
8
9
10
public class ClassName {

public void a(List<String> list){
// some code
}

public void b(List<Integer> list){
// some code
}
}

总的来说,泛型可以帮助我们提升代码的语义准确性,基于泛型设计类和方法,可以使得我们的代码更加灵活,可重用性更高。