一、理解泛型
在简单理解泛型之前,应该看下以下代码示例,当我们在代码中写下如下代码,将一个整型赋值给字符串变量时会报错
String x = 1;
会爆出以下错误:
Error:(63, 20) java: 不兼容的类型: int无法转换为java.lang.String
仅仅是一个简单的变量类型赋值尚会出现错误。在来举例一个常用例子,先看看Java标准库中的 ArrayList 其内部主要也是存储一个Object[]数组
public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {…}
public void remove(int index) {…}
public Object get(int index) {…}
}
当ArrayList中想要添加和获取String类型字符串时,需要在get时取出的Object对象强转为Sting对象,在这个过程中,有可能就转型错误,报错ClassCastException
ArrayList list = new ArrayList();
list.add(“Hello”);
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);
所以为了解决上面的问题,可以自己封装一个StrArrayList类,里面的add和get都是对String类型进行操作,但这样这个类只能保证存储获取的是String类的数据
public class StrArrayList {
private String[] array;
private int size;
public void add(String e) {…}
public void remove(int index) {…}
public String get(int index) {…}
}
但是各种自定义类和基础类会有茫茫多,ArrayList想要做到通用就不能针对每种情况单独写一个封装类,所以就需要吧ArrayList设置成一个模板ArrayList
public class ArrayList {
private T[] array;
private int size;
public void add(T e) {…}
public void remove(int index) {…}
public T get(int index) {…}
}
在这个其中的代码,T可以是任何类型的数据,就避免了重复定义类的问题。这就是泛型。简而言之,泛型就是一种可定义的类的模板。
泛型总结
泛型就是编写模板代码来自适应多种类型
泛型的好处使用时不需要类的强制转换
注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。
二、泛型使用
在泛型的自定义过程中,泛型类型不适用于静态方法,也就是如下类中,K不能替换为T,在create方法中使用,编译不会通过。或者说即使写了T编译通过,但使用中和Pair中的T也不是同一个T。
public class Pair {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { … }
public T getLast() { … }
// 静态泛型方法应该使用其他类型区分:
public static Pair create(K first, K last) {
return new Pair(first, last);
}
}
小结
编写泛型时,需要定义泛型类型;
静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法;
泛型可以同时定义多种类型,例如Map<K, V>。
三、泛型擦除
泛型是前文说到的,是一种模板代码的技术,可能不同语言实现方式不同。Java中使用的泛型实现方式是擦拭法,也就是虚拟机对泛型一无所知,所有工作都是编译器做的。
也就是我们写的代码如下
public class Pair {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
}
编译器进行处理,给到虚拟机的代码
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;
}
}
在使用过程中,编译器也将代码通过Ojbect对象强转为我们代码中定义的类,进行安全强转。
所以知道了Java的泛型是在编译器时执行的。了解了实现方法就知道了局限性。
局限一:T在虚拟机中运行其实是Ojbect类型,所以T中的数据不能放入基本类型
Pair p = new Pair<>(1, 2); // 这种就是编译报错,int要改成Integer包装类
局限二:因为T实际虚拟机中运行时Object,自己写的代码是String或自定义类,那么久无法获取类的class。
因为T是Object,我们对Pair和Pair类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。
换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair。
局限三:无法判断带泛型的类型
Pair p = new Pair<>(123, 456);
// 编译会失败,获取判断不了
if (p instanceof Pair) {
}
局限四:不能实例化T类型
小结
Java的泛型是采用擦拭法实现的;
擦拭法决定了泛型:
不能是基本类型,例如:int;### 不能获取带泛型类型的Class,例如:Pair.class;
不能判断带泛型类型的类型,例如:x instanceof Pair;
不能实例化T类型,例如:new T()。
泛型方法要防止重复定义方法,例如:public boolean equals(T obj);
子类可以获取父类的泛型类型。
四、extends通配符
使用类似<? extends Number>通配符作为方法参数时表示: