2020 年 5 月 30 日 泛型程序设计

用户头像
瑞克与莫迪
关注
发布于: 2020 年 05 月 30 日

本篇博客主要是对为什么使用泛型程序设计,泛型的简单定义,类型变量的限定,约束和局限性,泛型类型的继承原则、通配符类型,反射和泛型等进行阐述。



1、为什么使用泛型程序设计



泛型程序设计意味着编写代码可以被很多不同类型的对象所重用。泛型程序设计是用继承实现的

类型参数,编译器可以很好地利用该信息,当调用set和get的时候不需要强制类型转换

2、定义简单泛型类

定义:一个泛型类就是具有一个或多个类型变量的类,换句话说,泛型类就可以看作普通类的工厂

//定义公共类Pair
public class Pair<T>{
private T first;
private T second;
public Pair(){
first=null;
second=null;
}
public Pair(T first,T second){
this.first=first;
this.second=second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
}



3、泛型方法

泛型方法可以定义在普通类中,也可以定义在泛型类中。泛型方法的类型变量必须放在修饰符的后面,返回类型的前面。

例如

public static <T> T get Middle(T... a){
return a[a.length/2];
}

4、类型变量的限定

有时候类或方法需要对类型变量加以约束。例如将泛型T限定为实现了Comparable接口的类,可以通过对类型变量T设定实现这一点

public static <T extends Comparable> T min (T[] a)......

T 代表绑定类型的子类型。

T和绑定类型可以是类也可以是接口。选择extends关键字是因为更接近子类的概念

一个类型变量或通配符可以有多个限定

T extends Comparable & Serializable

5、泛型代码和虚拟机

虚拟机中没有泛型类型对象——所有对象都属于普通类

1、类型擦除

无论何时定义一个泛型擦除,都自动提供了一个相应的原始类型,原始类型的名字就是删除类型参数后的泛型类型名,擦除类型变量,并替换为限定类型。

原始类型用第一个限定的类型变量来替换,如何没有给定限定就用Object替换

2、翻译泛型表达式

当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。

当存取一个泛型域时也要插入强制类型转换

3、翻译泛型方法

类型擦除也会出现在泛型方法中。

泛型方法擦除容易带来的问题就是类型擦除与多态发生了冲突,要解决这个问题就需要编译器在类中生成一个桥方法。

注意:1、虚拟机中没有泛型,只有普通类和方法

2、所有的类型参数都用他们的限定类型替换

3、桥方法被合成保持来保持姿态

4、为保持类型的安全性,必要时插入强制类型转换

6、约束和局限性

下面的约束性和局限性大多数都是由类型擦除引起的



1、不能用基本类型实例化参数类型

不能使用基本类型的主要原因是类型擦除,擦除之后Object域并不能代替基本类型的域,不过可以尝试使用基本类型的包装器类型,如果不能解决问题,可以使用独立的类和方法处理他们

2、运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定非泛型类型,因此所有的类型查询只产生原始类型,如果视图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会的到一个编译器错误

3、不能创建参数化类型的数组

例如:Pair<String> table = new Pair<Stirng>[10]

4、Varargs警告

向参数个数可变的方法传递一个泛型类型的实例,会得到一个警告

5、不能实例化类型变量

不能使用像new T() ,new T[...]这样的表达式中的类型变量。

6、不能构造方泛型数组

7、泛型类型的静态上下文中类型变量无效

8、不能抛出或捕获泛型类型的实例

既不能抛出也不能捕获泛型类型对象。实际上设置泛型类扩展Throwable都是不合法的,不过在异常规范中使用类型变量是允许的

9、可以消除对受查异常的检查

10、注意擦除后的冲突

要想支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一个接口的不同参数化

7、泛型类型继承规则

注意:Pair<Employment> 和Pair<Manager>并不存在继承关系

ArrayList<String> 和List<T>之间存在继承关系

必须注意泛型和Java数组之间的重要区别:

数组是一种容器。尽管是最简单的一种容器,它也还算是个容器。其它的容器有:链表、栈、队列等。

泛型是一种模板技术,借助范型我们可以申明一个类型未知的类或者方法。比如说List<T>,这个T到底是什么类型呢?不知道。你具体实例化的时候才能确定它是什么类型,

例如:List<int> list = new List<int>();

8、通配符类型

1、通配符的概念

通配符类中,允许类型参数变化。

例如:通配符类型Pair< ? extends Employee >表示任何泛型Pair类型,它的类型参数必须是Employee的子类,如Pair<Manager>

Pair<Employee>和Pair<Manager>实际上是两种类型。



由上图可以看出,类型Pair<Manager>是类型Pair<? extends Employee>的子类型,所以为了解决这个问题可以把函数定义改成 public static void printEmployeeBoddies(Pair<? extends Employee> pair)

但是使用通配符会不会导致通过Pair<? extends Employee>的引用破坏Pair<Manager>对象呢?

例如:

Pair<? extends Employee> employeePair = managePair;employeePair.setFirst(new Employee("Tony", 100));

不用担心,编译器会产生一个编译错误。Pair<? extends Employee>参数替换后,我们得到如下代码

? extends Employee getFirst() void setFirst(? extends Employee) 对于get方法,没问题,因为编译器知道可以把返回对象转换为一个Employee类型。但是对于set方法,编译器无法知道具体的类型,所以会拒绝这个调用。



2、通配符的超类型限定

通配符限定与类型变量限定十分相似,但是还有一个附加能力,即可以指定一个超类型限定。如下:

? super Manager

这个通配符限定为Manager的所有超类型,可以为方法提供参数,但是却不能提供返回值。

例如,Pair<? super Manager>有方法

void setFirst(?super Manager);

? super Manager getFirst();

这不是真正的Java语法,但是可以看出编译器知道什么,编译器无法知道setFirst方法的具体类型,因此调用方法时不能接受类型为Employee或Object对象的参数,只能传递Manager类型的对象,或者某个子类型的对象。

如果调用getFirst,不能保证返回类型的对象,只能把它赋值给Object

直观的讲,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。



3、无限定通配符

例如: Pair<?> 有以下方法

? getFirst();

void setFirst();

getFirst的返回值只能赋值给一个Object,setFirst不能被调用,甚至不能用getFirst返回的Object调用。

Pair<?> 和Pair的本质区别在于:可以用任意Object对象调用原始Pair类的setObject方法。



4、通配符捕获

编写一个交换成对元素的方法:

public static void swap(Pair<?> p )

通配符不是类型变量,因此,不能在编写的代码中使用 “?”作为一种类型。下述代码是非法的:

? t = p.getFirst();

p.setFirst(p.getSecond());

p.setSecond(t);



可以通过使用如下方式进行编写

public static <T> void swapHepler(Pair <T> p) {
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}

注意:swapHelper是一个泛型方法,而 swap 不是一个泛型方法 ,它具有固定的Pair<?>类型的参数。

现在可以由swap调用swapHelper方法

public static void swap (Pair <?> P ) {swapHelper(p) }

在这种情况下,swapHelper方法的参数T捕获通配符。

9、反射和泛型

反射允许你在运行时分析任意的对象,如果对象是泛型类的实例,关于泛型类型参数则得不到太多的信息,因为他们会被擦除



1、泛型Class类

现在,Class类是泛型的。例如String.class实际上是Class<String>类的对象(事实上,是唯一对象)。

newInstance方法返回的一个实例,这个实例所属的类由默认的构造器获得。它的返回类型目前被声明为T ,其类型与Class<T>描述的类相同,这样就避免了类型转换。

如果这个类不是enum类或类型T的枚举值的数组,getEnumConstants就会返回null。



2、使用Class<T> 参数进行类型匹配

有时,匹配泛型方法中的Class<T>参数的类型变量很有实用价值。

实例:

public static <T> Pair makePair(Class<T> c) throws InstantiationException,IllegalAcessException{
return new Pair<>(c.newInstance , c.newInstance);
}

3、虚拟机中的泛型类型信息

Java泛型的卓越特性之一 是 在虚拟机中泛型类型的擦除。

类似地,看一下方法

public static Comparable min (Conparable[] a)

这是一个泛型方法的擦除

public static <T extends Comparable<? super T >> T min(T[] a)

可以使用反射API 来确定:

1、这个泛型方法有一个叫做T的类型参数

2、这个类型参数有一个子类型限定,其自身又是一个泛型类型

3、这个限定类型有一个通配符参数

4、这个通配符参数有一个超类型限定

5、这个泛型方法有一个泛型数组参数

为了表达泛型类型声明,使用java.lang.reflect包中提供的接口Type。这个接口包含以下子类型:

1、Class类,描述具体类型

2、TypeVariable接口,描述类型变量(如 T extends Comparable < ? super T>)

3、WildcardType接口,描述通配符(如 ? super T)

4、ParameterizedType接口,描述泛型类型类或接口类型 如(Comparable <? super T>).

5、GenericArrayType接口,描述泛型数组 如(T[])

注意:最后4个子类型是接口,虚拟机将实例化实现这些接口的适当的类。

用户头像

瑞克与莫迪

关注

还未添加个人签名 2020.05.18 加入

还未添加个人简介

评论

发布
暂无评论
2020年5月30日             泛型程序设计