Java泛型
什么是java泛型?
本质:参数化类型
即所操作的数据类型被指定为一个参数。
分泛型类、泛型接口、泛型方法
类型擦除
Java 泛型基本上都是在编译器这个层次来实现的。
在生成 Java 字节代码中(source–>bytecode 过程:编译过程)是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数会被编译器在编译的时候去掉,这就是类型擦除。(但会保证类或方法内部参数类型的一致性。即类名旁边带的 E,其内部方法使用到的 E,类型是一致的)
如 List<Object>和 List<String>等类型,在编译之后都会变成 List(泛型类对象的读取(类型转换)和写入(类型检查)的位置,编译器会自动帮我们添加约束)
缺陷:泛型类型不能显式地运用在运行时类型的操作当中,例如**转型、instanceof 和 new**。(运行时,所有参数的类型信息都丢失了)
擦除的补偿
类型判断问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class Building{}
class House extends Building{}
/**
* 类型判断器
*/
public class ClassTypeCapture{
Class kind;
public ClassTypeCapture(Class kind){
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args){
ClassTypeCapture ctt1 = new ClassTypeCapture(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture ctt2 = new ClassTypeCapture(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
//output
//true
//true
//false
//true创建类的实例
不能new T()原因,不能确定类型,不能确定T是否包含无参构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31//使用显式地工厂模式
interface IFactory{
T create();
}
class Foo2{
private T x;
public <T> Foo2(F factory){
x = factory.create();
}
}
class IntegerFactory implements IFactory{
public Integer create(){
return new Integer(0);
}
}
class Widget{
public static class Factory implements IFactory{
public Widget create(){
return new Widget();
}
}
}
public class FactoryConstraint{
public static void main(String[] args){
new Foo2(new IntegerFactory());
new Foo2(new Widget.Factory());
}
}
为什么需要泛型?
安全简单。可将运行时错误提前到编译时错误。
泛型之前是用Object的引用来实现参数的“任意化”,这种如果强制转化错误只能在运行时发现。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
通配符和上下界
通配符 ?(只能用在方法上,不能定义在类上会报错)
上界 ? extends T(生产者Producer)只能读,不能写(除了null);可以接收T及其所有子类类型的数据,这里T可以是类也可以是接口
下界 ? super T(消费者Consumer)只能写,不能读;可以接收T及其所有超类类型的数据。
类型擦除将会擦除到它的第一个边界(边界可以有多个)。编译器事实上会把类型参数替换为它的第一个边界的类型。如果没有指明边界,类型将被擦除到 object。
PECS:Producer extends Consumer super
例子:
1 | public static void main(String args[]) { |
null是所有引用类型都有的元素,所以上界(生产者)可以add成功
引入泛型之后的类型系统增加了两个维度:
- 类型参数自身的继承体系结构 (如
List<String>和List<Object>) - 泛型类或接口自身的继承体系结构(如
List接口继承自Collection接口)
**相同类型参数泛型类的关系取决于泛型类自身的继承体系结构 ** ( 即List<String>可以替换Collection<String>(Liskov 替换原则) )
当泛型类的类型声明中使用了通配符的时候,其子类型可以在两个维度上分别展开。
如对Collection<? extends Number>来说,其子类型可在 Collection 这个维度上展开,即 List<? extends Number> 和 Set<? extends Number>等。也可以在 Number这个维度上展开,即Collection<Double>和 Collection<Integer>等。如此循环下去,ArrayList<Long>和HashSet<Double>等也算是 Collection<? extends Number>的子类型
泛型的命名规范
E- Element,常用在 Collection 里,如:List<E>,Iterator<E>,Set<E>等K,V- Key,Value,代表 Map 的键值对N- Number,数字T- Type,类型- String,Integer 等 -
S,U,Vetc.
元组(tuple)类库
同list可用于数据存储,包含多个数据。可同时存储不同类型的数据类型。
1 | class TwoTuple<A,B>{ |
协变、逆变、不变
PECS:Producer-extends、Consumer-super
- 对于协变
? extends T,只能get(),即作为生产者(Producer) - 对于逆变
? super T,只能set(),即作为消费者(Consumer)
可变性是一种类型安全的方式,将一个对象当做另一个对象来使用。若不能将一个类型替换为另一个类型,那么这个类型称之为:不变量。
协变与逆变是相互对立的
- 若某个返回类型可由其派生类替换,则此类型是支持协变的。(修饰返回值。把子类指向父类的关系)
- 若某个参数类型可由其基类替换,则此类型是支持逆变的。(修饰传入参数。把父类指向子类的关系)
怎么自定义泛型接口、泛型类?
当声明或者实例化一个泛型的对象时,必须指定类型参数的值:
1 | Map<String, String> map = new HashMap<String, String>(); |
数组是协变的
泛型不是协变的
例:List不是List的父类型
泛型若是协变的会违反泛型提供的类型安全。
例:
1 | List<Integer> intList = new ArrayList<Integer>(); |