【一天一个基础系列】- java之泛型篇

说起各种高级语言,不得不谈泛型,当我们在使用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 }

泛型方法和类型通配符的区别

使用通配符比使用泛型方法(在方法签名中显式声明泛型形参)更加清晰和准确

类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzsjdw.html