1.概述
什么是泛型?为什么要使用泛型?
泛型,即“参数化类型”。所谓参数化类型就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。说白了就是要给你的代码增加健壮性,尽可能的在编译时就可以检测出BUG来。
泛型就是在定义类时,让类型变成参数。类型参数可以让你重复利用相同的代码,但是可以有不同的输入类型。与普通方法中的参数不同,方法中输入的是变量的值,而泛型输入的参数是类型。而且参数类型可运用在类、接口、方法中。
2.泛型的作用
- 使用泛型可以在编译器进行强类型检查时减少错误,毕竟修复编译错误可比修复运行时错误更容易。
- 使用泛型最常见的就是省去类型转换
举个例子,下面是没用泛型的代码,需要显示的类型转换
List list = new ArrayList();
list.add("hello world");
String s = (String) list.get(0);
使用泛型后,就不需要自己类型转换了。
List<String> list = new ArrayList<String>();
list.add("hello world");
String s = list.get();
- 可以写出泛型的算法
例如java集合框架,在使用泛型后,集合就可以应用在不同的类型上了。
3.定义泛型类型
下面举一个例子
基本book类
为了可以操作任何类型的对象,所用的属性类型是Object
public class Book{
private Object;
public void set(Object object){
this.object = object;
}
public Object get(){
return object;
}
}
由于它的方法接收和返回都是Object对象,你可以想传什么穿什么,传原始类型都可以。但是这样就没有就没有办法在编译阶段检察。
这时有可能set了一个Integer,但是get时以为是一个String,就会出现运行时错误。
泛型版本的Book类
class name<T1,T2,T3......Tn>{}
类型参数的部分跟在后面,用尖括号括起来,这表示类型参数(也叫类型变量)就是T1....T2,类型变量可以在类中的任何地方使用。
下面是用泛型重写的Book类
public class Book<T>{
//T代表Type的意思
T object;
public T getObject(){
return object;
}
public void setObject(T object){
this.object = object;
}
}
可以看到,以前的Object出现的地方呗T所替代。类型变量可以使任何的非原始类型,包括任何类,任何接口,任何数组,甚至是任何一个类型变量。
接下来演示一下使用泛型后和上面的区别
public static void main(String[] args){
BookT<Integer> b = new BookT<Integer>();
b.setObject(123);
//变化一,由于实例化时,已经制定了类型的实际参数是Integer,
//因此这里就只能set一个Integer,不能传入其他类型,
//编译器会帮助检察错误
Integer integer = b.getObject();
//变化二,这里不需要强制转换,因为编译器能判断这里类型就是 Integer
System.out.println(integer);
}
4.实例化一个泛型
需要将类定义中T替代成一个具体类型,如Integer
Book<integer> integerBook; 就像是传了一个参数一样。
Book<integer>也叫参数化的类型
实例化的代码:
Book<integer> integerBook = new Book<integer>();
5.钻石操作符 Diamond
JDK7以后,只要编译器可以根据上下文来推断出类型参数,在初始化时,就不必要将类型传给构造函数,使用空的尖括号就可以,这种尖括号也称为“钻石操作符”,如此,上面的实例化代码可以写成下面这样。
Book<Integer> integerBox = new Box<>();
在new后面的类上,就不用指定T的类型了,但是<>还是保留着,这个非常必要。
在多个类型在同样适用:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("ing", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
同样由于编译器可以推断类型,同样可以使用钻石操作符,上面的实例化可简化为:
Pair<String, Integer> p1 = new OrderedPair<>("ing", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");
泛型的raw类型
如果在实例化时,丢掉了<>,不向泛型类传递实际的类型会怎么样呢?
如果对于泛型的类,没有提供类型的实际参数,这就是泛型类的raw类型(raw type),使用raw类型,本质上退化成了泛型出现之前的代码,也就是Object的情况。
- 注意,普通的类,即非泛型类不是raw类
raw type 返回的是Object,这里为了向前兼容,将参数化的类型赋值给 raw 类型是合法的。但如果将一个raw类型,赋值给一个参数化的类型,将会收到一个警告。另外 如果使用raw type调用泛型方法,也会得到一个警告。
为什么会受到警告呢?因为编译器会进行强类型检查,当使用了raw type时,编译器没有足够的信息来做类型检查,所以就会报一个unchecked 的警告。所以尽量不要使用raw type。
6.泛型中的通配符
在使用泛型时,可以指定泛型是限定区域,例如:
必须是某某类的子类或某某接口的实现类,
<T extends 类或接口1 & 接口2>
类型通配符是使用?代替方法具体的类型实参。
-
<? extends Parent> 指定了泛型类型的上届
-
<? super Child> 指定了泛型类型的下届
-
<?> 指定了没有限制的泛型类型
注意
在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加
类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。