不能创建参数化类型的数组
不能实例化参数化类型的数组,例如:
Pair[] table =new Pair[10];//error
这有什么问题呢?擦除之后,table的类型是Pair[] ,可以把它转换为Object[];
Object[] objarray=table;
数组会记住他的元素类型,如果试图存储其他类型的元素,就会抛出一个Array-StoreException异常:
objerray[0]=”hello”; error,compnent type is pair
不过对与泛型类型,擦除会是这种机制无效,以下赋值:
objarray[0]=new Pair();
能够通过数组存储检查,不过仍会导致一个类型错误。出于这个原因,不允许创建参数类型的数组
也就是不允许:
Pair[] arr=new Pair[10];
需要说明的是,只是不允许创建这些数组,而声明类型为Pair[]的变量仍是合法的。不过不能用new Pair[10] 初始化这个变量
注释:可以声明统配类型的数组,然后进行类型转换:
Pair[] table=(Pair []) new Pair
public static <T> void addAll(Collection<T>coll ,T..ts)
{
for(t:ts)
coll.add(t)
}
应该记得,实际上参数ts是一个数组,包含提供的所有实参。
现在考虑以下调用:
Collection< Pair<String>> table= ...;
Pair<String> pair1=...;
Pair<String> pair2=...;
addAll(table,pair1,pair2);
为了调用这个方法,java虚拟机必须建立一个pair数组,这就违反了前面的规则,不过对于这种情况,规则有所放松,你只会得到一个警告,而不是错误
Collection —–就是一个指向数组的类型参数变量.也就是某种特殊形式的数组
可以采用两种方法来抑制这个警告。一种方法是为包含addAll调用的方法增加注解@SuppressWarnnings(“unchecked”)
或者在java se7,还可以用@SafeVarargs直接标注addAll方法:
@SafeVarargs
public static void addAll(Collection coll , T…ts)
现在,就可以提供泛型类型来调用这个方法了。对于只需要读取参数数组元素的所有方法,都可以使用这个注解,这仅限于最常见的用例
Collection< Pair<String>> table= ...;
Pair<String> pair1=...;
Pair<String> pair2=...;
addAll(table,pair1,pair2);
注释:
可以使用@SageVarargs标注来消除创建泛型数组的有关限制,方法如下:
@SafeVarargs static <E> E[] array(E...array)
{
return array;
}
现在可以调用:
Pair table=array(pair1,pair2) ;
这看起来方便,不过应藏着危险,以下代码:
Object[] ob=table;
ob[0]=new Pair<Employee>;
能顺利的运行而不会出现ArrayStoreException异常,应为数组存储会看擦出的类型,但是在处理table[0]时,你会在被处得到一个异常。
不能实例化类型变量。
不能使用像new T(…),new T[…]或者T.class这样的表达式中的类型变量。例如,下面的Pair 构造器就是非法的
public Pair()
{
first=new T(); //error
second=new T();
}
类型擦除将T改变成Object,而且,本意肯定不希望调用new Object()。在Java SE 8 之后,最好的解决办法是让调用者提供一个构造表达式
Pair p=Pair.makePair(String::new)
makePair方法接受一个Supplier,这是一个函数式接口,表示一个无参数而且返回类型为T的函数
public static <T> Pair<T> makePair(Supplier<T> constr)
{
return new Pair<>(constr.get(),constr.get());
}
比较传统的解决方法是通过反射调用Class.newInstance方法构造泛型对象:
遗憾的是,细节有点复杂,不能调用
first=T.class.newInstance();//error
表达式T.class是不合法的,因为他会擦除为Object.class。必须向下面这样设计API以便得到一个Class对象:
public static <T> Pair<T> makePair(Class<T> cl)
{
try
{
return new Pair<>(cl.newInstance(),cl.newInstance());
}
catch(Exception ex)
{
return null;
}
}
这个方法可以按照下列方式调用:
Pair p=Pair.makePair(String.class);
注意:Class类本身是泛型。例如:String.class是一个Class的实例(事实上,他是唯一的实例)
因此,makePair方法能够判断出pair的类型。
不能构造泛型数组
就像不能实例化一个泛型实例一样,也不能实例化数组。不过原因有所不同,毕竟数组会充填null值,构造是看上去是安全的。不过,数组本身也有类型,用来监控存储在虚拟机中的数组。这个类型会被擦除,例如,考虑下面的例子:
public static <T extends Comparable> T[] minmax(T[] a)
{
T[] mm=new T[2]; //error
}
类型擦除会让这个方法永远构造Comparable[2]的数组
如果数组仅仅作为一个类的私有实例域,就可以将这个数组成名为Object[],并且在获取元素时进行类型转化。例如:ArrayList类可以这样实现。
public class ArrayList<E>
{
private Object[] elements;
...
@SuppressWarnnings("unchecked")
public E get(int n)
{
return (E) elements[n];
}
}
实际的实现没有这么清晰:
public class ArrayList<E>
{
private E[] elements;
...
public ArrayList()
{
elements=(E[])new Object[10];
}
}
这里,强制类型转换是一个假象,而类型擦除使其无法察觉
由于minmax方法返回T[] 数组,使得这一技术无法施展,如果掩盖这个烈性会有运行时的错误:
假设实现代码:
public static <T extends Comparable> T[] minmax(T...a)
{
Object[] mm=new Object[2];
...
return (T[])mm; //complies with warning
}
调用String[] ss=ArrayAlg.minmax(“Tom”,”Disk”,”Harry”);
编译时不会有任何警告,当Object[]引用赋值给Comparable[] 变量时,将会发生ClassCastException异常。
在这种情况下最好让用户提供一个数组构造其表达式
String [] ss=ArrayAlg.minmax(String[]::new,”Tom”,”Disk”,”Harry”);
构造其表达式String::new指示一个函数,给定所需的长度,会构造一个指定长度的String数组。
minmax方法使用这个参数生成一个有正确类型的数组:
public static <T extends Comparable> T[] minmax( IntFunction<T[]>constr,T...a)
{
T[] mm=constr.apply(2)
}
比较老实的方法是利用反射,调用Array.newInstance:
public static <T extends Comparable> T[] minmax(T...a)
{
T[] mm=(T[])Array.newInstance(a.getClass().getComponentType(),2);
...
}
ArrayList 类的toArray方法就没有那么幸运,他需要生成一个T[]数组,但没有成分类型。因此,有下面两种不同的形式:
Object[] toArray()
T[] toArray(T[] result)
第二种方法接受一个数组参数,如果数组足够大,就是用这个数组,否则,用result的成分类型构造一个足够大的新数组。
泛型类型的静态上下文中类型变量无效
不能再静态域或者方法中引用类型变量。例如,下列高照将无法施展:
public class Singleton<T>
{
private static T singleInstance; //error
public static T getSingleInstance()
{
if(singleInstance==null)
construct new instance of T
return singleInstance;
}
}
如果这个程序能够运行,就可以声明一个Singleton 共享随机数生成器。声明一个Singleton共享文件选择器对话框,但是这个程序无法工作,类型擦除后,只剩下Singleton类,他质保哈你个singleton类,只包含一个singleInstance域。因此,禁止使用带有类型变量的静态域和静态方法。
不能抛出或者捕获泛型类的实例
既不能抛出也不能捕获泛型类对象。实际上,甚至泛型类扩展Throwable都是不合法的。例如,下面定义就不能正常编译。
public class Problem extends Exception //error — can’t extends Throwable
catch子句中不能使用类型变量。例如,以下方法将不能编译
public static <T extends Throwable> void doWork(Class<T> t)
{
try
{
...
}
catch(T e) //error --can't catch type variable
{
Logger.gobal.info(...)
}
不过,在异常规范中使用类型变量是允许的。
以下方式是合法的
public static <T extends Throwable> void doWork(T t) throws T //OK
{
try
{
do work
}
catch(Throwable realCause)
{
t.initCause(realCause);
throw t;
}
}
可以消除对受查异常的检查
Java异常处理的一个基本原则就是,必须为所有受查异常提供一个处理器。不过可以利用泛型消除这个限制。关键在于以下方法:
@SuppressWarnnings("unchecked")
public static <T extends Throwable> void throwAs(Throwable e) throws T
{
throw (T)e;
}
假设这个方法包含在类Block中,如果调用
Block. throwAs(t);
编译器就会认为t是一个非受查异常。以下代码会将所有的一场都转换为编译器所认为的非受查异常。
try
{
do work //将所有的异常转换为RuntimeException(非受查异常)
}
catch(Throwable t)
{
Block.<RuntimeException> throwAs(t);
}
下面把这个代码包装在一个抽象类中,用户可以覆盖(在此段代码中覆盖body方法来提供一个具体的操作,这样在调用toThread时候,会得到Thread累的一个对象,他的run方法不会介意受查异常(因为转为非受查异常)
public abstract class Block
{
public abstrct void body() throws Exception;
public Thread toThread()
{
return new Thread() //匿名类
{
public void run()
{
try
{
body();
}
catch(Throwable t)
{
Block.<RuntimeException>throwAs(t);
}
}
};
}
@SuppressWarnnings("unchecked")
public static <T extends Throwable> void throwAs(Throwable e) throws T
{
throw (T) e;
}
}
例如:以下程序运行了一个线程,他会抛出一个受查异常
public class Test
{
public static void main(String[] args)
{
new Block()
{
public void body() throws Exception
{
Scanner in=new Scanner(new File("q"),"UTF-8");
while(in.hasNext())
System.out.println(in.next());
}
}.toThread().start();
}
}
运行这个程序,会得到一个栈轨迹,其中包含一个FileNotFoundException(前提是没有提供一个名为q的文件)
package NEW_DATE_SEQUENCE_PACKAGE;
import java.awt.Component;
import java.io.File;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Scanner;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class J_8_30_1
{
public static void main(String[] args)
{
new Block()
{
public void body() throws Exception
{
Scanner in=new Scanner(new File("q"),"UTF-8");
while(in.hasNext())
System.out.println(in.next());
}
}.toThread().start();
}
public static abstract class Block
{
public abstract void body() throws Exception;
public Thread toThread()
{
return new Thread() //匿名类
{
public void run()
{
try
{
body();
}
catch(Throwable t)
{
Block.<RuntimeException>throwAs(t);
}
}
};
}
@SuppressWarnings("unchecked")
public static <T extends Throwable> void throwAs(Throwable e) throws T
{
throw (T) e;
}
}
}
这有什么意义呢?正常情况下,你必须捕获线程run方法中的所有受查异常,把他们包装到非受查异常中,因为run方法声明为不跑出任何异常。
不过这里并没有做这种包装,我们只是抛出异常,并哄骗编译器,让他认为这不是一个受查异常、
通过使用泛型类、擦除和@SuppressWarning注解,就能消除java类型系统的部分限制
注意擦除后的冲突
当泛型类型被擦除时,无法创建引发冲突的条件。下面是一个示例:假定像下面这样将equals方法添加到Pair类中:
public class Pair<T>
{
public boolean equals(T value)
{
return first.equals(value)&&second.equals(value);
}
}
考虑一个Pair
从概念上讲,他有两个equals方法:
boolean equals(String) //defined in Pair<T>
boolean equals(Object) // inherited from Object
但是,直觉把我们引入歧途。方法擦除
boolean equals(T)
等同于
boolean equals(Object)
与Object.equals方法发生冲突
当然,补救的方法是重新命名引发错误的方法。
反省规范的说明还提到另外一个原则:要想支持擦除转换,就需要强行限制一个类或者类型变量不能同时成为两个接口类型的子类,而这两个接口是统一接口的不同参数化。
例如:
Class employee implements Comparable<Employee>{};
class manager extends employee implements Comparable<Manager>{} //error
因为Manager会实现Comparable和Comparable,这是同一个接口的不同参数化
这一限制与类型擦除的关系不十分明确,毕竟,下列非泛型版本是合法的。
class Employee implements Comparable{}
class Manager extends Employee implements Comparable{}
其原因很微妙,有可能与合成的桥方法产生冲突。实现了Comparable的类可以获得一个桥方法:
public int comparaTo(Object other)
{
return compareTo((X) other);
}
对于不同类型的X不能有两个这样的方法。
泛型类型的继承规则
在使用泛型时,需要了解一些有关继承和子类型的准则。下面从许多程序员感觉不太直观的情况开始。考虑一个类和一个子类,如Employee和Manager。Pair< Manager>时Pair< Employee>的一个子类码?不是,或许人们感到很奇怪。例如,下面的代码不能编译成功:
Manager[] topHonchos=…;
Pair< Employee> result =ArrayAlg.minmax(topHonchos); //error
minmax方法返回Pair< Manager>,而不是Pair,并且这样的赋值不是合法的。
无论S和T有什么联系,通常,Pair < S>和Pair< T> 没有什么联系
这一限制看起来过于严格,但是对于类型安全非常必要。假设允许将Pair< Manager>转换为Pair< Employee>,考虑下面代码:
Pair< Manager> managerBuddies=new Pair<>(ceo,cfo);
Pair< Employee> employeeBuddies=managerBuddies;
employeeBuddies.setFirst(lowlyEmployee)
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取