java泛型
1、为什么需要泛型?
(1)在编译的时候提供安全检测的功能
(2)同时能支持多种类型参数。
概念有点抽象,举个例子比如List 我们在使用的时候,可以指定不同的类型。
List<String> strList=new ArrayList<>();
List<Integer> intList=new ArrayList<>();
为什么能支持这样呢,看ArrayList的源码,可以看到E就是不确定的类型,需要外部指定。
public class ArrayList<E> extends AbstractList<E>{
}
那它又如何进行安全检测呢。比如此时给intList添加一个字符串类型的元素。此时编译器会标红报错,提醒类型错误。
list2.add("111");
2、泛型的分类
泛型可以分为三种,分别是泛型类,泛型接口,泛型方法。
ArrayList就是一个典型的范型类。这里自定义一个泛型类。
public class Generic<T>{ //T可以换成任意字母
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
当外部创建这个类传入指定类型时,这个类中的所有T类型会被自动替换成该类型。如下例子所示,将这个Generic中的T类型转化成了String类型。
Generic<String> generic=new Generic<>("11");
泛型接口,典型例子就是List,可以看到源码里是这样写的。
public interface List<E> extends Collection<E>
这里再自定义一个泛型接口。
public interface Generator<T> {
public T next();
}
再创建一个类实现这个接口
有两种方式
1)实现类没指定具体类型时,在声明时需要包含泛型
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
2)实现类中指定了接口的类型。
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
类型通配符
比如有一个方法
public void myFun(List<String> list){
}
这时候很明显,只能传List<String>的,而不能传List<Integer>类型。如果我不想指定某一个具体类型时,可以借助类型通配符?。
public void myFun(List<?> list){
}
这样就可以传入不同类型的List而不会报错了。
myFun(new ArrayList<String>());
myFun(new ArrayList<Integer>());
泛型方法
并不是泛型类中包含了泛型符号的方法就是泛型方法,如下所示的方法就不是泛型方法。
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
真正的泛型方法需要 在返回值和权限修饰符中用<T>对方法表明是一个泛型方法。如下所示只有包含在<>中的才能当作泛型符号使用,并且可以同时使用多个。
public <T> T myFun(T object) {
return object;
}
泛型方法和可变参数
泛型方法还可以不指定形参的具体个数,通过...代表可变参数 。如下所示,此时arr相当于数组。
public <T> void myFun(T... arr) {
for (T t:arr){
System.out.println(t);
}
}
3、泛型的限定
(1) 作用:为了限定只能传入某种类型的父类或者某种类型的子类。
(2)为泛型添加上边界,即传入的类型实参必须时指定类型的子类型。如下例所示。
public void myFun(Generic<? extends Number> obj){
}
myFun(new ArrayList<Integer>());
myFun(new ArrayList<Double>());
myFun(new ArrayList<Long>());
(4)和<? extends Number>对应的是<? super Number>,指的是必须时Number的父类才能传入。
(5)Extends 后面可以是类也可以是接口,可以有多个通过&连接。但如果同时有接口或者类,类有且只能有一个,必须在第一个。
T,V extends ArrayList&Comparable
4、泛型数组
如何实现一个泛型数组?
如果直接像这样创建是不行的,编译器会提示错误,会提示Generic array creation。
Observable<String>[] arr=new Observable<String>[10];
但是如果通过通配符写,则可以。
Observable<?>[] arr=new Observable<?>[10];
5、泛型的限制
1 泛型不能实例化 如T a=new T();
2 静态变量类型或者静态方法的返回类型不能是泛型,但本身是泛型方法的时候可以
private T a;
private static T a;//报错
private static T fun(T a){//报错
return a;
}
private static <T> T fun(T a){//本身是泛型方法时不报错
return a;
}
3 泛型不能是基本类型
4 泛型不能使用instanceof
5 泛型的class对象是原生类型,和传入的T类型无关
System.out.println(restrict.getClass()==restrictString.getClass());
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());
6 泛型类不能创建数组
7 泛型类不能继承自Throwable和Exception也不能被try catch捕获
6 泛型的继承规则
1 假定Worker继承自Employee,那么Pair<Employee>和Pair<Worker>没有任何继承关系
2 泛型类之间可以继承,比如List和ArrayList
7 通配符
通配符用?表示,和T的作用类似,用于安全地访问数据。
区别在于:?只能用在方法上,而T可以在方法、类上
<? extends Worker>不能传入类型 只能取出Worker类型 协变
<? super Employee> 能输入类型,但只能传出object类型。逆变
如下所示 往GenericType<? extends Fruit>内传Fruit和它子类都会报错,注意和 GenericType<Fruit>的区别,这种方式可以直接传入子类。
原因是<? extends Fruit>未确定指定泛型是哪一种类型,如 GenericType<Fruit>确定指定了泛型是Fruit,因为<? extends Fruit>未指定类型,所以编译器为了安全考虑,会禁止传入类型,而只允许读取值。且读取时,只能知道能够存入 GenericType<? extends Fruit>都是Fruit的子类,因此用Fruit及其父类接受都是安全的。
GenericType<? extends Fruit> fruitGenericType = new GenericType<>();
fruitGenericType.setData(new Apple()); //编译错误
Apple apple=fruitGenericType.getData();//编译错误
Fruit fruit=fruitGenericType.getData();//编译正确
GenericType<Fruit> fruitGenericType2 = new GenericType<>();
fruitGenericType2.setData(new Apple());
详情见https://blog.csdn.net/zy_jibai/article/details/90082239
8泛型擦除
List<T>在运行时,会被擦除类型成为List<Object>原生类型。因此strList.getClass()==intList.getClass()总是相等的。所有的泛型都会被编译成一个class文件。编译器在Java虚拟机内通过signature属性,在字节码内记录保存相关的泛型信息,因此泛型类型是弱记忆的,不是完全擦除。