Java泛型
其本质是参数化类型,就是所操作的数据类型被指定为一个参数(type parameter),这种参数可以用在类、接口、和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
泛型作用
- 泛化:可以用T代表任意类型
- 类型安全:泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制。如果不用泛型,则必须使用强制类型转换。
- 消除强制类型转换:可以消除源代码中许多强制类型转换,使代码更加易读
使用泛型注意事项
1、使用泛型时,泛型类型必须是引用数据类型,不能为基本数据类型。Java中的普通方法、构造方法、静态方法中都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。
2、运行时类型检查,不同类型的泛型类是等价的
3、泛型类不可以继承Exception类,即泛型类不可以作为异常被抛出
4、不可以定义泛型数组
5、不可以用泛型构造对象,first = new T()错误
6、在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰
常见的泛型的类型表示
常见的参数通常有 :
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(表示Java 类,包括基本的类和我们自定义的类)
- K - Key(表示键,比如Map中的key)
- V - Value(表示值)
- ? - (表示不确定的java类型)
泛型最常用的场景是集合和接口
泛型集合
以ArrayList为例:
如果不定义泛型,泛型类型实际上就是Object类型,使用时需要进行强制转换
List list = new ArrayList();
list.add("name");
list.add(20);
list.add(2.5);
String obj = (String)list.get(0);
Integer age = (Integer)list.get(0);
如果定义了泛型<String>,我们就可以直接使用,无需进行转换
List<String> list = new ArrayList();
list.add("hello");
list.add("world");
String hello = list.get(0);
String world = list.get(0);
泛型接口
可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。
public interface Comparable<T> {
public int compareTo(T o);
}
泛型类
编写一个泛型类:先按正常的类进行编写,然后全部用T进行替换;
public class Speak<T> {
private T type;
private T level;
public Speak(T type, T level) {
this.type = type;
this.level = level;
}
public T getType() {
return type;
}
public T getLevel() {
return level;
}
}
需要注意的是,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T
会编译错误,如图:
多个泛型类型
public class Product<T,K> {
private T name;
private K num;
public Product(T name, K num) {
this.name = name;
this.num = num;
}
public T getName() {
return name;
}
public K getNum() {
return num;
}
}
java语言的泛型实现方式是擦拭法,意思是指虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
例如我们编写的Speak<T>,编译器看到的代码:
public class Speak<T> {
private T type;
private T level;
public Speak(T type, T level) {
this.type = type;
this.level = level;
}
public T getType() {
return type;
}
public T getLevel() {
return level;
}
}
虚拟机看到的代码:
public class Speak {
private Object type;
private Object level;
public Speak(Object type, Object level) {
this.type = type;
this.level = level;
}
public Object getType() {
return type;
}
public Object getLevel() {
return level;
}
}
Java使用擦拭方实现泛型,做的工作:
- 编译器把类型<T>视为Object
- 编译器根据<T>实现安全的强制转型
Java的泛型是编译器在编译时实行的,编译器内部永远把所有类型的T当作Object处理,但是在需要转型的时候,编译器会根据T的类型自动为我们实行安全的强制转换。
了解了Java泛型的原理,我们就知道了Java泛型的局限:
1、<T>不能是基本类型,如int,因为实际类型是Object,Object无法持有基本类型
List<int> list =new ArrayList<>(); //编译错误
2、无法取得带泛型的Class
Speak<String> str = new Speak<String>("hello","world");
Speak<Integer> ss = new Speak<String>(123,456);
Class p = str.getClass();
Class t = ss.getClass();
System.out.println(p == t); //true
System.out.println(p == Speak.class); //true
因为T是Object类型,我们对Speak<String>和Speak<Integer>获取Class时,获取到的是同一个Class,就是Speak的Class。
所有的泛型实例,getClass返回同一个Class实例。
3、无法判断带泛型的类型
Speak<Integer> p = new Speak<>(123, 456);
// Compile error:
if (p instanceof Speak<String>) {
}
4、不能实例化T类型
new T() 不允许
泛型继承
一个类可以继承自一个泛型类
public class Chinese extends Speak<T>{
}
在父类是泛型类型的情况下,编译器就必须把类型T保存到子类的class文件中,不然编译器就不知道chinse只能存取T的类型。因此在继承了泛型类型的情况下,子类可以获取父类的泛型类型。
extends 通配符
public static void main(String[] args) {
Speak<Integer> str = new Speak<>(234,900);
Speak<Double> ss = new Speak<>(234,900);
int a = add(str);
int b = add(ss);
System.out.println(a);
}
static int add(Speak<? extends Number> p) {
Number first = p.getType();
Number last = p.getLevel();
return first.intValue() + last.intValue();
}
除了可传入Speak<Integer> ,还有Speak<Double>,因为Integer和Double都是Number的子类。
只能接受该类型以及子类。
使用extends 限定T的类型
在定义泛型Speak<T>的时候,也可以使用extends 通配符限定T的类型:
public class Speak<T extends Number>{
}
super通配符
public static void main(String[] args) {
Speak<Integer> str = new Speak<>(234,900);
Speak<Number> str = new Speak<>(234,900);
int a = add(str);
System.out.println(a);
}
static int add(Speak<? super Integer> p) {
Number first = p.getType();
Number last = p.getLevel();
return first.intValue() + last.intValue();
}
可以传入Integer的父类Number
只能接受该类型以及父类
extends和super的对比
public class Collections {
// 把src的每个元素复制到dest中:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i);
dest.add(t);
}
}
}
参考文档: