Java-泛型程序设计
一.为何要使用泛型:
可以被很多不同类型的对象所重用。比那些直接使用Object变量,然后强制类型转换的代码具有跟好的安全性和可读性。
使用类型参数好处:
1.可以将需要使用的类型提前声明,如:ArrayList<String> newlist = new ArrayList<String>();
2.可以告知这个类适用于什么类型,当调用对应的get()方法的时候,不需要进行强制类型转换,编译器本事就知道其对应的类型。
当实现一个泛型的时候非常不容易,因为你需要知道这个这个类对应的所有用途及其类型,所以java提供了通配符类型,来解决这个问题。
二.定义简单泛型类
泛型类,就是指具有一个或者多个类型变量,也就是说这个类适应这几种类型,对于之后在那类来说,我们只关注泛型,而不会为数据类型转换烦恼。
类型变量使用大写形式,且比较短,这是很常见的。在java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字和值的类型。T(需要时还可以用临近的字母U和S)表示类型。
使用类型变量T,用<>括起来,放在类名后面。这个泛型可以有多个类型变量,如<T,U>。
三.定义泛型方法
泛型方法既可以在普通类中,也可以在泛型类中,定义方式是在方法名前加<T> T,说明该方法是泛型方法。如下:
class ArrayAlg { public static<T> T getMiddle(T... a) { return a[a.length / 2]; } }
当调用一个泛型方法时, 在方法名前的尖括号中放入具体的类型。如下:
String middle = ArrayAlg.<String>getMiddle("john", "Q.", public ");
方法调用中可以省略类型参数, 编译器有足够的信息能偶推断出所调用的方法,如下:
String middle = ArrayAlg.getMiddle("john", "Q.", public ");
对于泛型方法的引用都没有问题。 偶尔, 编译器也会提示错误。如下,编译器将会自动打包参数为 1个 Double 和 2个Integer 对象,而后寻找这些类的共同超类型。事实上, 找到2个这样的超类型:Number 和 Comparable 接口, 其本身也是一个泛型类型。 在这种情况下, 可以采取的补救措施是 将 所有的参数写为 double 值。
double middle = ArrayAlg.getMiddle(3.14, 0, 1729);
四.类型变量的限定
有的时候,比如对于特定的方法执行特定的操作,但是该操作不适用于一些类型,这时可以对类型变量T设置限定,可以使其集成特别的类或者接口(在这里对于接口也是使用继承,因为使用extends更加接近子类的意思)
一个类型变量或通配符可以有多个限定,限定类型用“&” 分隔,而用逗号来分隔类型变量。在java继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,但必须是第一个。如:T extends Comparable & Srializable
五.泛型代码和虚拟机
Java泛型转换的事实:
JVM中没有泛型,只有普通的类和方法。
所有的类型参数都用他们的限定类型替换(如果没有类型参数,则使用Object类型),这个过程称为擦除(erased),擦除类型变量,并替换为限定类型。
桥方法被合成来保持多态。
有时为保持类型安全性,需要插入强制类型转换。
六.约束与局限
使用Java泛型时需要考虑一些限制,这些限制大都是由类型擦除引起的。
1.不能用基本类型实例化类型参数:
不能用类型参数来代替基本类型。就是没有Pair<double>,只有Pair<Double>。当然主要是原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double的值。
2.运行时类型查询只适用于原始类型:
虚拟机中的对象总有一个特定的非泛型类型。因此,所有类型查询只产生原始类型。
如:if(a instanceof Pari<String>)是错误的,因为只能查询原始类型,即Pari,if(a instanceof Pari<T>)是错误的
3. 不能创建参数化类型的数组:
Couple<Employee>[] couple = new Couple<Employee>[5] ; 这种声明式不合法的。如果要存放参数化类型对象的集合,可以考虑使用ArrayList<Couple<Employee>>进行声明,而且Java中建议优先使用集合,这样既安全又有效。
4.Varargs警告:
向参数个数可变的方法传递一个泛型类型的实例,为调用这个方法,虚拟机必须建立一个Pair<String>数组,这就违反了前面的规则,不过,对于这种情况有所放松,只会得到一个警告,而不是错误。
可以使用注解@SuppressWarnings("unchecked")或@SafeVarargs抑制这个警告。
5.不能实例化类型变量:
不能使用像new<T>(...),new t[...]或T.class这样的表达式中的类型变量。
6.泛型类的静态上下文中类型变量无效:
不能在静态域或方法中引用类型变量。
7.不能抛出或捕获泛型类的实例:
在Java中, public class GenericException <T> extends Exception {...} 这种泛型类扩展子Throwable是不合法的,不能通过编译器。不能再catch子句中使用类型参数。
java 异常处理的一个基本原则是,必须为所有已检查异常提供一个处理器。不过可以利用泛型消除这个限制。
8.注意擦除后的冲突
七.泛型类型的继承规则
在使用泛型时,需要了解一些有关继承和子类型的准则。
如:Manager是Employee的子类,那么Pair<Manager>是Pair<Employee>的一个子类吗? 答案是:“不是”
规则:无论S与T有什么联系。通常,Pair<S>与Pair<T>没有什么联系。
永远可以将参数化类型转换为一个原始类型。

八.通配符类型
1.为什么使用通配符:
固定的泛型类型系统使用起来并没有那么友好,Java的设计者发明了一种巧妙的解决方法:通配符类型。
泛型通配符(?)和类型变量(T)的区别——在任何泛型类型(泛型变量声明中)vs某一种泛型类型(泛型类实现中)?可以在声明变量时使用,例如:
Pair<Manager> managerBuddies=new Pair<>(ceo,cfo);
Pair<? extends Employee> wildcardBuddies=managerBuddies;
2.通配符概念:
通配符类型中,允许类型参数变化。
例如:通配符类型:Pair<? extends Employee>
表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair<Manager>,但不是Pair<String>
即前面说到的问题:不能将Pair<Manager>传递给Pair<Employee>,解决方法是:使用通配符类型。
Pair<? extends Employee>中的方法似乎是这样:
? extends Employee getFirst()
void setFirst(? extends Employee)
这样将不能调用setFirst方法,编译器只知道需要某个Employee的子类型,但不知道具体是什么类型,所以拒绝传递任何特定的类型。
用getFirst就不存在这个问题:将getFirst的返回值赋给一个Employee的引用完全合法
这就是引入有限定的通配符的关键之处。

3.通配符的超类型限定
通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定:? super Manager。这个通配符限制为Manager的所有超类型
引入super的原因:带有超类型限定的通配符的行为之前介绍的的Pair<? extends Employee>行为恰恰相反,可以为方法提供参数,但不能使用返回值。
直观来说,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
4.无限定通配符
还可以使用无限定的通配符,如:Pair<?>,它有以下方法:
? getFirst()
void setFirst()
getFirst的返回值只能赋给一个Object。setFirst方法不能被调用。
5.通配符捕获
通配符不是类型变量,因此不能在编写代码时使用“?”作为一种类型,如:? t=p.getFirst();
如何临时保存一个通配符类型变量,需要一个辅助方法,如下:
public static <T> void swapHelper(Pair<T> p) { T t=p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); }
这里使用swapHelper方法的参数T捕获通配符。
通配符捕获只有在有许多限制的情况下是合法的,编译器必须确定通配符表达的是单个、确定的类型。如:ArrayList<Pair<?>>中的通配符就不能通过ArrayList<Pair<T>>捕获。数组列表可以保存两个Pair<?>,分别针对?的不同类型。
九.反射和泛型
反射允许你在运行时分析任意对象。但如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除。
下面将了解利用反射可以获得泛型类的什么信息。
1.使用Class参数进行类型匹配
现在,Class类是泛型的,类型参数十分有用,这是因为它允许Class<T>方法的返回类型更加具有针对性。
Class<T>中的方法就使用了类型参数:
T newInstance()——返回一个实例,这个实例所属的类由默认的构造器获得。
T cast(Object obj)——如果给定的类型确实是T的一个子类型,cast方法就会返回一个现在声明为类型T的对象
T[] getEnumConstants()——如果这个类不是enum类或类型T的枚举值的数组,返回null
Class<? super T>get SuperClass()
Constructor<T> getConstructor(Class... parameterTypes)
Constructor<T> getDeclaredConstructor(Class... parameterTypes)
有时,匹配泛型方法中的Class<T>参数的类型变量很有实用价值:
public static <T> makePair(Class<T> c) throws InstantiationException { return new Pair<>(c.newInstance(),c.newInstance()); }
如果调用makePair(Employee.class),makePair方法的类型参数T同Employee匹配,并且编译器可以推断出这个方法将返回一个Pair<Employee>
3.虚拟机中的泛型类型信息
Java泛型的卓越特性之一是在虚拟机中泛型类型的擦除。令人感到奇怪的是,擦除的类仍然保留一些泛型祖先的微弱记忆。例如,原始的Pair类知道源于泛型类Pair<T>,但不知道具体的类型变量。
类似地,方法:public static Comparable min(Comparable[] a)
这是一个泛型方法的擦除:public static <T extends Comparable<? super T>> T min(T[] a)
可以使用反射API来确定:
*这个泛型方法有一个叫做T的类型参数
*这个类型参数有一个子类型限定,其自身又是一个泛型类型
*这个限定类型有一个通配符参数
*这个通配符参数有一个超类型限定
*这个泛型方法有一个泛型数组参数
可以重新构造实现者声明的泛型类以及方法中的所有内容。但是,不会知道对于特定的对象或方法调用,如果解释类型参数。
为了表达泛型类型声明,使用java.lang.reflect包中提供的接口Type,这个接口包含下列子类型:
*Class类,描述具体类型
*TypeVariable接口,描述类型变量(如T extends Comparable<? super T>)
*WildcardType接口,描述通配符(如? super T)
*ParameterizedType接口,描述泛型类或接口类型(如Comparable<? super T>)
*GenericArrayType接口,描述泛型数组(如T[])

浙公网安备 33010602011771号