说起各种高级语言,不得不谈泛型,当我们在使用java集合的时候,会发现集合有个缺点:把一个对象“丢进”集合之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,改对象的编译类型就变成了Object类型
问题1:集合对元素类型没有任何限制,这样可能会引发一些问题,比如创建一个用于保存A对象的集合,但不小心把B对象放进去,会引发异常
问题2: 由于把对象放进去时,集合对视了对象的状态信息,集合只知道它盛装的是Object,因此去取集合元素后通常还需要进行强制类型装换,这个过程不仅增加了编程的复杂度,还可能引发CLassCastException异常
为解决以上问题,便引入“泛型”
java 5以后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型
java 7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型
比如
//java 7之前 List<String> list = new ArrayList<String>();//后面的<String>是必须带上的 //java 7之后,"菱形"语法 List<String> list = new ArrayList<>();注:java 9允许在使用匿名内部类时使用菱形语法
概念定义:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方式动态地指定
我们来看一下定义泛型接口、类
/** * 定义泛型接口,实质:允许在定义接口、类时什么类型形参, * 类型形参在整个接口、类体内可当成类型使用,几乎所有可 * 使用普通类型的地方都可以使用这种类型形参 */ public interface List<T> { void add(T x); } /** * 定义 * * */ @Data public class Clazz<T> { private T a; public Clazz(T a){ this.a = a; } } //使用Clazz pulic void method(){ Clazz<String> clazz = new Clazz<>(""); }
从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含泛型形参
//定义类Son类继承Parent类 public class Son extends Parenet<T>{ } //使用Parent类时为T形参传入String类型 public class Son extends Parent<String>{ } //使用Parent类时,没有为T形参传入实际的类型参数 public class Son extends Parent{ }像这种使用Parent类时省略泛型的形式被称为原始类型(raw type)
如果从Parent类派生子类,则在Parent类中所有使用T类型的地方都将被替换成String类型
并不存在泛型类
List<String>与List<Integer> 创建出来的是同样class文件,它们在运行时总有同样的类,故在静态方法、静态初始化块或者静态变量的生命和初始化中不允许使用泛型形参
public class R<T>{ //错误,不能在静态变量声明中使用泛型形参 static T info; //错误,不能再静态方法声明中使用泛型形参 public void foo(T p){ } }
类型通配符
定义:为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型
类型通配符的上限
定义:当直接使用List<?>·这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个List<?>`是任何泛型List的父类,只希望它代表某一类泛型List的父类
//定义上限为Parent类,表示泛型形参必须是Parent子类 List<? extends Parent>协变:对于更广泛的泛型类来说,指定通配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样A<Foo>就相当于A<? extends Bar>的子类,可以将A<Foo>赋值给A<? extends Bar>类型的变量,这种型变方式被称为协变
类型通配符的下限
定义:通配符的下限用<? super类型>的方式来指定,通配符下限的作用与通配符上限的作用恰好相反
//定义下限为Parent类 List<? super Parent>逆变:比如Foo是Bar的子类,当程序需要一个A<? super Foo>变量时,程序可以将A<Bar>、A<Object>赋值给A<? super Foo>类型的变量,这种型变方式被称为逆变
对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。口诀是:逆变只进不出
泛型方法
定义:所谓泛型方法,就是在声明方法时定义一个或多个泛型形参,与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数修饰符<T,S>返回值类型 方法名(形参列表){ //TODO }
泛型方法和类型通配符的区别
使用通配符比使用泛型方法(在方法签名中显式声明泛型形参)更加清晰和准确
类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明