摘要:泛型方法泛型类中可以定义静态非静态的泛型方法。上述泛型类会被替换成下面形式一般使用第一个限定类型替换变为原始类型,没有限定类型,使用替换。
引言
在面向对象的世界里,我们如果需要一个容器来盛装对象。举个例子:一个篮子。我们可以用这个篮子装苹果,也可以用这个篮子装香蕉。基于 OOP 的思想,我们不希望为苹果和香蕉分别创建不同的篮子;同时,我们希望放进篮子里的是苹果,拿出来的还是苹果。于是,Java 程序员提出了「泛型」的概念——一种类似于 C++ 模板的技术。
早期程序员使用如下代码创建一个泛型集合:
</>复制代码
public class ArrayList{
private Object[] elementData;
...
public Object get(int i);
public void add(Object o);
}
我们可以看出,对与这个集合而言,取出 (get) 和放入时都没有进行类型检查。因此,如果我们不记得放入的顺序,把取出的对象进项强制类型转换,很可能出现 ClassCastException。因此,真正的泛型是可以在编译时期,对数据类型进行检查,保证安全。如下面代码所示:
</>复制代码
ArrayList list = new ArrayList<>();
P.S. <>里的String叫做类型参数。
使用泛型,为我们提供了如下优点:
更强大的编译时期的类型检查
避免不必要的类型转换,如:
</>复制代码
List list = new ArrayList<>(3);
String str = list.get(0);
让程序能够实现通用的算法
泛型类泛型中使用名为泛型参数表明可以传入类或方法的对象类型,这种设计实现了类型参数化(可以把同一类型的类作为参数进行传递),如下面的代码所示:
泛型类示例
</>复制代码
public class Pair{
private T first;
private T last;
public Pair(){}
public Pair(T first, T last){
this.first = frist;
this.last = last;
}
public T getFirst();
public T getLast();
}
泛型方法示例
</>复制代码
public class Util{
// 简单的泛型方法
public static T getMiddle(T...a){
return a[a.length/2];
}
// 带限定符的泛型方法,如果有多个限定符,使用 & 连接多个接口或超类
public static T min(T...a){
// 具体实现
}
}
注意,这里的泛型参数(type parameter)在上述示例中指的是用大写字母 T 表示的值,而泛型实参(type argument)则是指 T a 中的 a。根据惯例,泛型参数通常如下命名:
E:表示一个元素,Java 集合框架中使用最多
K:键
N:数字
T:类型
V:值
S,U,V:其它类型
原始类型(raw type)原始类型指的是,不包括泛型参数的类型,如上述泛型类中的 Pair。我们可以通过原生类型构造对象:
</>复制代码
Pair pair = new Pair();
同时,可以通过泛型参数构造对象:
</>复制代码
Pair pair = new Pair<>();
但是,如果把一个通过原生类型获取的对象指向一个通过泛型参数生成的参数会报 unchecked warning,如下面的代码:
</>复制代码
Pair pair = new Pair();
Pair pair1 = pair;
继承和子类型
在 Java 中,有继承的概念,简而言之,就是一个类型可以指向它的兼容类型,如:
</>复制代码
Object object = new Object();
Integer integer = new Integer(20);
object = integer;
上述代码表示:Integer IS-A Object。这种概念在泛型中也适用。如下定义:
</>复制代码
public class Box{
public void add(T t);
}
那么一个 Box 的对象可以增加任意 Number 子类的值。但是 Box
泛型类中可以定义静态、非静态的泛型方法。泛型方法的语法为:<泛型参数类型列表> + 返回类型 + 泛型参数列表。
静态方法
</>复制代码
public static void foo(T t){
}
非静态方法
</>复制代码
public void foo(T t){
}
类型限定
在某种情况下,我们希望方法只接受特定类型的参数,可以使用如下语法实现:
</>复制代码
public void inspect(U u){
// 这里是逻辑处理
}
上述代码中,该泛型方法只接受为 Number 类型的参数。同样,也可以在泛型类上加以限制:
</>复制代码
public class Utils{
// 这里的 T 必须为 Number 类型
private T t;
}
当然,也可以使用多重限制,如下面代码所示:
</>复制代码
public class Utils{
}
P.S. 限制中的类必须放在接口的前面。
类型推断类型推断是:编译器去推断调用方法的参数的类型的能力。
如,泛型方法中:
</>复制代码
public void addBox(Box box){
// 这里是处理代码
}
不必通过 obj.addBox(box) 调用, 可以省略。
构造方法中:
</>复制代码
// 类型推断
Map> map = new HashMap<>();
其中,构造方法中的泛型还可以这样用:
</>复制代码
// 定义泛型类
public class Box{
public Box(T t){
}
}
// 实例化一个对象
public class Application{
void method(){
Box box = new Box<>(");
}
}
通配符
通配符 ? 表示一个未知的类型,可用于参数的类型、字段以及局部变量中,但不可用于调用泛型方法里的类型参数、泛型对象实例化以及泛型超类里。
</>复制代码
// 可以
public void foo(Pair pair){
// 可以
Pair foo;
}
// 可以
private Pair pair;
上界通配符
上界通配符表明需要最高限定的类型,下面的代码用来计算所有类型为数字的集合的总和:
</>复制代码
public double sumList(List){
// 这里做逻辑处理
}
无界限通配符
使用无界限通配符表示不确定的类型,以下两种情况可以使用无界限通配符:
当方法的参数可以用 Object 对象替换
方法的实现不依赖具体的类型
比如,有一个打印集合对象的方法:
</>复制代码
// 定义一个打印集合对象列表的方法
public void printList(List list){
for(Object obj: list){
// 打印list
}
}
// 调用方法
List integers = Arrays.asList(1,2,3);
List strings = Arrays.asList("A","B","C");
printList(integers);
printList(strings);
P.S. List 和 List