泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。一个 ArrayList 类可以聚集任何类型的对象,这是一个泛型程序设计的实例。
类型参数的好处
ArrayList< String> files = new ArrayList < > ( ) ;
当调用 get 的时候,不需要进行强制类型转换,编译器就知道返回值类型为 String:
String filename = files.get(0);
编译器还知道 ArrayList 中 add 方法有一个类型为 String 的参数。可以避免插入错误类型的对象。
files.add(new File("…")); //error
类型参数的魅力在于:使得程序具有更好的可读性和安全性。
实现泛型类
对于类型参数,需要内置所有的类,需要预测出所有类的未来可能有的所有用途。
ArrayList 类有一个方法 addAll 用来添加另一个集合的全部元素。
定义泛型类
public class Name < T, U> { private T Name1; private U Name2; }
泛型方法
class ArrayAlg { public static < T> T getMiddle ( T. . . a) { return a[ a. length / 2 ] ; } }
-- -
String middle = ArrayAlg. < String> getMiddle ( "John" , "Q." , "Public" ) ;
类型变量的限定
public static < T extends Comparable > T min ( T[ ] a) . . .
类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用 Object)。
public class Interval < T extend Comparable & Serializable> { . . . }
public class Interval ;
为提高效率,应该将标签接口(没有方法的接口)放到边界列表的末尾。
翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
编译器将泛型方法调用翻译为两条指令:
翻译泛型方法
虚拟机中没有泛型,只有普通的类和方法。 所有的类型参数都用它们的限定类型替换。 桥方法被合成来保持多态。 为保持类型安全性,必要时插入强制类型转换。
问题1:
public class pair { . . . ; public void setSecond ( Object second) { this . second= second; } }
class DateInterval extends pair {
public void setSecond ( LocalDate second) { . . . ; } }
DateInterval interval = new DateInterval ( . . . ) ;
Pair< LocalDate> pair = interval;
pair. setSecond ( aDate) ;
当子类对象被泛型父类调用时,类型擦除将会与多态发生冲突。要解决这个问题,需要在子类中生成一个桥方法覆盖原父类方法:public void setSecond(Object second) {setSecond((Date) second)};
问题2:
若 DateInterval 方法也覆盖了 getSecond 方法,则在此类中将出现两个返回值不同 getSecond 方法。
但在虚拟机中,用参数类型和返回类型确定一个方法,这个问题能够得到解决。
在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。原方法与覆盖方法称为具有协变的返回的类型。合成的桥方法调用了新定义的方法。
约束与局限性
不能用基本类型实例化类型参数 运行时类型查询只适用于原始类型 不能创建参数化类型的数组
Varargs警告
不能实例化类型变量
public Pair ( ) { first = new T ( ) ; second = new T ( ) ; }
Pair< String> p = Pair. makePair ( String: : new ) ;
-- -
public static < T> Pair< T> makePair ( Supplier< T> constr)
{ return new Pair < > ( constr. get ( ) , constr. get ( ) ) ; }
public static < T> Pair< T> makePair ( Class< T> cl)
{
try { return new Pair < > ( cl. newInstance ( ) , cl. newInstance ( ) ) ; }
catch ( Exception ex) { return null; }
}
-- -
Pair< String> p = pair. makePair ( String. class ) ;
public static < T extends Comparable > T[ ] minmax ( T[ ] a) { T[ ] mm = new T [ 2 ] ; . . . }
String[ ] ss = Array. minmax ( String[ ] : new , "Tom" , "Dick" , "Harry" ) ;
public static < T extends Comparable > T[ ] minmax ( IntFunction< T[ ] > constr, T. . . a)
{ T[ ] mm = constr. apply ( 2 ) ; }
public static < T extends Comparable > T[ ] minmax ( T. . . a)
{ T[ ] mm = ( T[ ] ) Array. newInstance ( a. getClass ( ) . getComponentType ( ) , 2 ) ; . . . }
泛型类的静态上下文中类型变量无效 不能抛出或捕获泛型类的实例
public static < T extends Throwable > void doWork ( Class< T> t) {
try { . . . }
catch ( T e) {
. . . } }
public static < T extends Throwable > void doWork ( T t) throws T
{
try { }
catch ( Throwable realCause) {
t. initCause ( realCause) ;
throw t;
}
}
@SuppressWarning ( "unchecked" )
public static < T extends Throwable > void throwAs ( Throwable e) throw T { throw ( T) e; }
假设该方法包含于 Block 中,通过调用该类静态方法即可将异常包装为非受查异常。
try { do work} catch ( Throwable t) { Block. < RuntimeException> throwAs ( t) ; }
将以上代码包装在一个抽象类。用户可以覆盖 body 方法来提供一个具体的动作。调用 toThread 时,会得到 Thread 类的一个对象,它的 run 方法不会介意受查异常。
public abstract class Block {
public abstract void body ( ) throws Exception;
public Thread toThread ( ) {
return new toThread ( ) {
public void run ( ) {
try {
body ( ) ;
} catch ( Throwable t) {
Block. < RuntimeException> throwAs ( t) ;
} } } ; }
@SuppressWarning ( "uncheck" )
public static < T extends Throwable > void throwAs ( Throwable e) throws T {
throw ( T) e;
}
public static void main ( String[ ] args) {
new Block ( ) {
public void body ( ) throws Exception{
Scanner in = new Scanner ( new File ( "ququx" ) , "UTF-8" ) ;
while ( in. hasNext ( ) )
System. out. println ( in. next ( ) ) ;
} } . toThread ( ) . start ( ) ; } }
运行单元测试时,会得到一个栈轨迹,其中包含一个 FileNotFoundException。
正常情况下,你必须捕获线程 run 方法中的所有受查异常,把它们包装到非受查异常中,因为 run 方法声明不抛出任何受查异常。
注意擦除后的冲突
当泛型类型被擦除时,无法创建引发冲突的条件。
例如:一个泛型类擦除前为T,此时他与父类具有两个同名不同参的方法。
boolean equals(T); // 内部定义
boolean equals(Object); // 父类继承
// 参数不同未覆盖
擦除后 T 被限定为 Object,此时,两个方法就变成同名同参的方法,无法正确调用。
解决办法就是重新命名引发错误的方法。
一个类或类型变量不能同时成为两个接口类型的子类,因为这两个接口时同一接口的不同参数化。
class Employee implement Comparable< Employee> { . . . }
class Manager extends Employee implements Comparable < Manager> { . . . }
泛型类型的继承规则
无论 S 与 T 有什么联系,通常 Pair 与 Pair 没什么联系。对于类型安全非常重要。将Pair赋给Pair不合法。
数组容许将子类对象赋给父类变量,但此时试图将父类对象加入数组时,将会报错。
在将参数化类型转换为一个原始类型后,给参数化域赋值时会抛出异常。
通配符
通配符类型中,允许类型参数变化
Pair< ? extends Employee >
public static void printBuddies ( Pair< ? extends Employee > p)
? extends Employee getFirst ( ) ;
void setFirst ( ? extends Employee ) ;
Pair< Manager> managerBuddies = new Pair < > ( . . . ) ;
Pair< ? extends Employee > wildcardBuddies = managerBuddies;
wildcardBuddies. getFirst ( ) ;
wildcardBuddies. setFirst ( lowlyEmployee) ;
超类型限定
Pair< ? super Manager>
void setFirst ( ? super Manager) ;
? super Manager getFirst ( ) ;
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
public interface Comparable < T> { public int compareTo ( T other) ; }
public static < T extends Comparable < T> > T min ( T[ ] a)
public static < T extends Comparable < ? super T>> T min ( T[ ] a) . . .
int compareTo ( ? super T)
无限定通配符
Pair< ? >
? getFirst ( )
void setFirst ( )
其对于许多简单的操作非常有用,当操作不需要实际的类型时(判空)。
通配符捕获
通配符不是类型变量,不能使用 ?作为一种类型。
public static < T> void swapHelper ( Pair< T> p)
public static void swap ( Pair< ? > p)
{ swapHelper ( p) ; }
通配符捕获只有在许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。ArrayList<Pair> 中的 T 永远不能捕获 ArrayList <Pair<?>>中的通配符。数组列表可以保存两个 Pair<?> ,分别针对 ? 的不同类型。