一、泛型初衷
Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,这要就具有很好的通用性。但这样做也带来两个问题:
–集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常。
–由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既会增加编程的复杂度、也可能引发ClassCastException。
二、什么是泛型
所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
值得注意的是并不存在泛型类
虽然可以把ArrayList<String>类当成ArrayList的子类,事实上ArrayList<String>类也确实是一种特殊的ArrayList类,这个ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。
实际上,泛型对其所有可能的类型参数,都具有同样的行为,从而可以把相同的类被当成许多不同的类来处理。与此完全一致的是,类的静态变量和方法也在所有的实例间共享,所以在静态方法、静态初始化、或者静态变量的声明和初始化中不允许使用类型形参。
系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。
三、深入泛型
1.定义泛型接口、类
定义Apple类时使用了泛型声明
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
|
// 定义Apple类时使用了泛型声明
public
class
Apple<T>
{
// 使用T类型形参定义实例变量
private
T info;
public
Apple(){}
// 下面方法中使用T类型形参来定义构造器
public
Apple(T info)
{
this
.info = info;
}
public
void
setInfo(T info)
{
this
.info = info;
}
public
T getInfo()
{
return
this
.info;
}
public
static
void
main(String[] args)
{
// 由于传给T形参的是String,所以构造器参数只能是String
Apple<String> a1 =
new
Apple<>(
"苹果"
);
System.out.println(a1.getInfo());
// 由于传给T形参的是Double,所以构造器参数只能是Double或double
Apple<Double> a2 =
new
Apple<>(
5.67
);
System.out.println(a2.getInfo());
}
}
|
四、类型通配符
使用List<?>这种形式是,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。
我们需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,为了满足这种需求,Java泛型提供了被限制的泛型通配符。被限制的泛型通配符的如下表示:List<? extends Shape> ,List<? super Shape>
五、擦除与转换
在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。
当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。
比如说一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了成类型变量的上限(即Object),这种情况被为擦除。