目录
1.泛型的定义
在引出泛型之前,我们首先来看这样一个案例:
我们知道,Object 类是所有类的基类,因此我们如果创建一个 Object 类型的数组,那么我们就可以往里面添加任意类型的元素。我们以包装类为例,分别添加不同的包装类,在遍历数组时都按 String 类型对其元素进行使用,那么运行时就会报出异常。
public static void main(String[] args) {
//创建一个Object类数组
Object[] objects = new Object[10];
//为Object数组给定不同包装类型元素
objects[0] = (Integer)12;
objects[1] = "aaa";
objects[2] = (Double)12.5;
//遍历输出这个数组
for (int i = 0; i < objects.length; i++) {
String a = (String)objects[i];
System.out.println(a);
}
}
对于类似情形——把类型明确的工作推迟到创建对象或调用方法的时候,我们就可以引入泛型来解决这个问题。
泛型是在 JDK 1.5 中引入的一个新特新,就是所谓的 “ 参数化类型 ” 。将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参), 然后在使用/调用时传入具体的类型(类型实参)。也就是说在泛型使用过程中, 操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2.泛型的使用
需要注意的是:泛型只能为引用数据类型,对于 8 大基本数据类型要使用对应的包装类。
(1)泛型类:泛型类也就是把泛型定义在类上,这样用户在使用类的时候才把类型给确定下来。类中的泛型会根据已确定的类型自动转化,因此无需在编写程序时对类型做强制转换。
①定义一个泛型类
//泛型类的定义:在类名后加<T>,表示未指定类中成员类型
public class Generic<T> {
private T attribute;//未指定类型的成员属性
private Integer age = 12;//普通的成员属性
public void getType(T param) {//未指定参数类型的成员方法
String typeName = param.getClass().getName();//获取参数类型名
System.out.println("方法参数类型为:" + typeName);
attribute = param;//属性默认为null时无法判断类型
String typeAttribute = attribute.getClass().getName();//获取attribute类型名
System.out.println("成员属性attribute类型为:" + typeAttribute);
String typeAge = age.getClass().getName();//获取age类型名
System.out.println("成员属性age类型为:" + typeAge);
}
}
②使用泛型类
public static void main(String[] args) {
//实例化一个泛型类:必须在类名后声明具体类型
//此时所有 T 类型均变为 String 类型
Generic<String> generic = new Generic<String>();
//调用泛型类的getType方法,此时所有T
generic.getType("111");//此时的参数类型必须是 String,若为其他报错
}
③运行结果
方法参数类型为:java.lang.String
成员属性attribute类型为:java.lang.String
成员属性age类型为:java.lang.Integer
(2)泛型方法:有时我们不关心类而是只关心方法,此时就可以用把此方法单独写成一个泛型方法。泛型方法在调用该方法时明确具体的类型。
public class Start {
public static void main(String[] args) {
//调用泛型方法
show("a");//字符串
show(12);//Integer
show(new Integer[]{1,2,3,5});//Integer数组
}
//泛型方法:在方法修饰符后面有一个 <T>
public static <T> void show(T t){
String str = t.getClass().getName();
System.out.println("参数值为:"+t);
System.out.println("参数类型为:"+str);
}
}
结果为:
参数值为:a
参数类型为:java.lang.String
参数值为:12
参数类型为:java.lang.Integer
参数值为:[Ljava.lang.Integer;@1b6d3586
参数类型为:[Ljava.lang.Integer;
注意事项:
①泛型方法必须带有<T>(<>中的内容可任意,但首字母必须大写),且<T>必须写在方法的修饰符之后。
②泛型方法和泛型类中带有泛型参数的方法是不一样的。泛型方法在调用方法时确定参数具体的类型,而泛型类中的方法在创建类时就已经确定了参数具体的类型。
③在泛型类中可以定义泛型方法,但该方法的泛型与类的泛型是相互独立的,在方法调用时单独确定。
public class Generic<T> {
//未指定参数类型的成员方法
public void getType1(T param) {
System.out.println("执行带泛型的成员方法。。。。");
String typeName = param.getClass().getName();//获取参数类型名
System.out.println("方法参数类型为:" + typeName);
}
//泛型方法
public <T> void getType2(T param){
System.out.println("执行泛型方法");
String typeName = param.getClass().getName();//获取参数类型名
System.out.println("方法参数类型为:" + typeName);
}
}
public static void main(String[] args) {
//规定泛型类型为Integer
Generic<Integer> generic = new Generic<>();
//未指定参数类型的成员方法
generic.getType1(123);//参数只能为Integer
//泛型方法
generic.getType2("1234");//参数可任意引用类型
}
④泛型方法能使方法独立于类而产生变化,因此尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化, 那么就应该使用泛型方法。
⑤泛型类在创建对象时确定具体类型。因此对于类中带泛型的成员属性和成员方法,不能使用 static 关键字。要想在泛型类中使用方法的静态能力,就必须使用泛型方法。
(3)泛型接口:泛型接口的定义方式与泛型类基本相同,在实现类实现泛型接口时分为明确类型实现和不明确类型实现。
//定义一个泛型接口
public interface Interface<T> {
public void show(T param);
}
//明确类型:此时这个实现类就是一个普通的实现类(泛型已经明确类型)
public class InterfaceImpl1 implements Interface<String>{
@Override
public void show(String param) {
//方法体
}
}
//不明确类型:类名后要加上<T>
public class InterfaceImpl2<T> implements Interface<T>{
@Override
public void show(T param) {
//方法体
}
}
3.泛型通配符的使用
public static void main(String[] args) {
Collection<String> list1 = new ArrayList<String>();
list1.add("111");
list1.add("222");
list1.add("333");
Collection<Integer> list2 = new ArrayList<Integer>();
list2.add(1);
list2.add(2);
list2.add(3);
}
当我们遇到上面所示的情形:有两个不同类型的集合,但是要用一个方法来实现对集合元素的遍历。
此时无法在定义这个方法时确定传入集合泛型的具体类型,那么把传入的集合泛型定义为 Object 类可以吗?显然是不可以的。
尽管 Object 类是所有类的父类,但 Collection < Object > 并不是 Collection < String > 或者 Collection < Integer > 的父类,那么该如何让解决这个问题呢?
我们可以引入泛型通配符 < ? > 来解决这一类型的问题。其实 ?在泛型中起到了和 Object 类相同的作用,可以把它看成是泛型中所有具体类型的父类,因此它作为具体类型是一个实参而不是形参。
//定义一个方法遍历任意类型的集合
//从逻辑上讲就是把所有泛型的集合都当做它们父类 ? 进行遍历类
public static void show(Collection<?> collection) {
Iterator<?> iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
泛型通配符的强大不止于此,泛型通配符还有其独特的过滤功能——泛型通配符的上下限:
上限:< ? extends A > 此时只能接受 A 或者 A 的子类
下限:< ?super B > 此时只能接受 B 或者 B 的父类
我们拿上下限来举个例子:Number 是 Integer 的父类,且是 Object 的子类。
此时可以看出,对于上限为 Number 的集合,Object 就不能作为参数传入;对于下限为 Number 的集合,Integer 就不能作为参数传入。通过通配符的上下限,我们在编写程序时就自动过滤了不想要的参数类型。
4.泛型的作用
(1)类型安全。引入泛型的主要目的就是提高 Java 程序的类型安全。泛型的实现方式,支持泛型几乎不需要 JVM 或类文件更改,所有工作都在编译器中完成,编译器生成的类没有泛型(和强制类型转换),只是来确保数据类型安全;
(2)消除强制类型转换。泛型一个附带好处是,消除代码中许多强制类型的转换。减少代码出错率,更好阅读;
(3)潜在的性能收益,可以带来更好的优化可能。