Thinking in Java 中关于泛型的讲解篇幅实在过长,前后尝试阅读这一章,但总是觉得找不到要点,很迷。于是放弃 Thinking in Java 泛型一章的阅读,转而官方教程,本章可以算作官方教程的中文版。
放上官方教程地址:
1.为什么使用泛型
简单来说,泛型使类型在定义类、接口和方法时成为参数。就像在方法声明中使用形式参数一样,类型参数提供了一种使用不同输入重用相同代码的方法。不同之处在于形式参数的输入是值,而类型参数的输入是类型。
使用泛型的代码比非泛型代码有许多好处:
在编译时进行更强大的类型检查。
Java编译器将强类型检查应用于通用代码,并在代码违反类型安全时发出错误。修复编译时错误比修复运行时错误更容易,后者很难发现错误源头。消除转型
以下没有泛型的代码片段需要强制转换:
List list = new ArrayList(); list.add("hello"); String s = (String)list.get(0);
使用泛型时,不需要类型转换:
List <String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); //没有转型
使程序员能够实现通用算法。
通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读。
2. 泛型类
泛型类是对类型进行参数化的类或接口。 下面一步步展示该概念。
2.1 简单的 Box 类
如果我们想在一个类中存放任何类型的对象,怎么做呢?没错,使用 Object 即可。
下面展示一个可对任何类对象进行操作的非泛型 Box 类:
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
由于它的方法接受或返回一个Object
,所以你可以自由地传入任何你想要的东西。 在编译时无法验证类的使用方式。 代码的一部分可能会放置一个Integer
,并期望从中获取Integer
,而代码的另一部分可能会错误地传入String
,从而导致运行时错误。
2.2 Box 类的泛型版本
上面提到,通过 Object 存储,不存在任何类型信息,这可能导致使用时类型错误。于是泛型发挥作用了。
泛型类定义格式如下:
class name<T1, T2, ..., Tn> { /* ... */ }
用尖括号将类型参数包起来,并跟在类名后面。
于是2.1中的代码修改之后如下:
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
如代码所示,所有 Object 都被 T 替换。类型变量可以是制定的任何非基本类型:类、接口、数组或者其他类型变量。且类型变量 T 可以在类的任何位置使用。
同样,也适用于将泛型应用于接口,如下:
interface Box<T> { /*...*/}
2.3 类型参数命名约定
按照惯例,类型参数名称是单个大写字母 。
最常用的类型参数(标识符)名称是:
- E - Element(Java Collections Framework广泛使用)
- K - key
- N - number
- T - 类(类型)
- V - value
- S,U,V等 - 第2,第3,第4类型
2.4 调用和实例化泛型类
将 T 替换为某些具体类即可,例如 Integer:
Box<Integer> integerBox = new Box<Integer>();
//在 Java SE 7 及更高版本中,只要编译期可以从上下文中确定或推断类型参数,就可以用一组空的类型参数“<>” 替换调用泛型类的构造函数所需的类型参数
//如下:
Box<Integer> integerBox = new Box<>();
泛型类的调用通常称为参数化类型
2.5 多种类型参数
泛型类可以有多个类型参数,如下展示一个通用的 OrderPair 类,实现了 Pair 接口:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class