1、泛型擦除
ArrayList<String> ArrayList<Integer> 我们可能认为类型不同,但其实它们是相同的
ArrayList<Integer> arrays= new ArrayList<>();
ArrayList<String> arrays1= new ArrayList<>();
arrays.add(2);
arrays1.add("s");
// true
System.out.println(arrays.getClass().equals(arrays1.getClass()));
// true
System.out.println(arrays.getClass() == (arrays1.getClass()));
Class.getTypeParameters()将返回一个TypeVariable对象数据,表示有泛型声明所声明的类型参数。但是我们从输出中只能发现用作参数占位符的标识符。
因此:在泛型代码内部,无法获得任何有关泛型参数类型的信息。
public static void main(String[] args) {
ArrayList<Integer> arrays= new ArrayList<>();
HashMap<Integer, Double> maps= new HashMap<>();
// E
System.out.println(Arrays.toString(arrays.getClass().getTypeParameters()));
// K V
System.out.println(Arrays.toString(maps.getClass().getTypeParameters()));
}
我们可以知道诸如类型参数标识符和泛型类型边界这类信息——但是我们无法知道用来创建某个特定实例的实际的类型参数。
Java泛型是使用擦除来实现的,意味着我们使用泛型时,任何具体的类型信息都被擦除,我们只知道我们在使用一个对象。
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界,比如List<T>将被擦除为List,普通类型变量没有指定边界会被擦除为Object
使用擦除的正当理由:从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。 擦除使得先有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。因为它不会突然间破坏所有现有代码。
擦除的代价是显著的,泛型不能用于显示地引用运行时类型的操作之中,例如转型、instanceof和new表达式。因为所有关于参数的信息都丢失了。我们在使用泛型时必须时刻提醒自己 我们只是看起来好像拥有有关参数的类型 信息而已。
3、擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切类型的操作都无法完成
public static void f(Object arg){
// error
if(arg instanceof T){
}
// error
T var = new T();
// error
T[] array = new T[SIZE];
//error
T t = (T) new Object[SIZE];
}
因此有时必须通过引入类型标签来对擦除进行补偿。需要显式传递你的类型的Class对象,以便在类型表达式中使用它们。
通过动态的isInstance来使用
class Building{}
class House extends Building{}
class ClassTypeCapture<T>{
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> buildingClassTypeCapture = new ClassTypeCapture<>(Building.class);
// true
System.out.println(buildingClassTypeCapture.f(new Building()));
// true
System.out.println(buildingClassTypeCapture.f(new House()));
ClassTypeCapture<House> House = new ClassTypeCapture<>(House.class);
// false
System.out.println(House.f(new Building()));
// true
System.out.println(House.f(new House()));
}
}
编译器将确保类型标签可以匹配泛型参数
4、泛型类中创建一个new T()无法实现的部分原因是因为擦除,另一部分原因是编译器不能验证T具有默认(无参)构造器。Java的解决方案是传递一个工厂对象,并用来创建新的实例。
class ClassAsFactory<T>{
T x;
public ClassAsFactory(Class<T> kind) {
try{
x = kind.newInstance();
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
class Employee{}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> employeeClassAsFactory = new ClassAsFactory<>(Employee.class);
System.out.println("ccccc succed");
try{
ClassAsFactory<Integer> integerClassAsFactory = new ClassAsFactory<Integer>(Integer.class);
} catch (Exception e){
// java.lang.InstantiationException: java.lang.Integer
System.out.println("failed:"+e);
}
}
}
可以编译,但是会由于ClassAsFactory<Integer>失败,因为Integer没有任何默认的构造器。因为这个错误不是在编译器捕获的,因此SUn建议使用显式的工厂,并限制类型。
interface Factory1<T>{
T create();
}
class Foo2<T>{
private T x;
public <F extends Factory1<T>> Foo2(F factory){
x = factory.create();
}
}
class IntegerFactory implements Factory1<Integer>{
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget{
public static class Factory implements Factory1<Widget>{
@Override
public Widget create(){
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
传递Class<T>的一种变体。两种方式都传递了工厂都系,Class<T>是内建的工厂对象,上面显式创建了一个工厂对象,可以获得编译时检查。
另一种方式为模版方法设计模式。get()是模版方法,create是在子类中定义的、用来产生子类类型的对象。
abstract class GenericWithCreate<T>{
final T element;
public GenericWithCreate() {
element = create();
}
abstract T create();
}
class X{}
class Creator extends GenericWithCreate<X>{
@Override
X create(){return new X();}
void f(){
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator creator = new Creator();
creator.f();
}
}
5、泛型数组
一般来说不能创建泛型数组,解决方案为创建泛型数组的地方使用ArrayList
public class ListOfGeneric {
private List<T> array = new ArrayList<T>();
public void add(T item){array.add(item);}
public T get(int index){return array.get(index);}
}
如果仍旧希望创建泛型数组的话,可以按照编译器喜欢的方式定义一个引用
class Generic<T> {
}
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
static final int SIZE = 100;
public static void main(String[] args) {
// java.lang.ClassCastException
// gia = (Generic<Integer>[]) new Object[SIZE];
gia = (Generic<Integer>[]) new Generic[SIZE];
// Generic[]
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<Integer>();
// 编译时异常
// gia[1] = new Object();
// // 编译时异常
// gia[2] = new Generic<Double>();
}
}
由于数组跟踪它们的实际类型,这个类型是数组被创建时确定的,因此gia这个泛型数组已经被转型为Generic<Integer>[],但是这个信息只存在编译器,在运行时,它仍旧是Object数组,会有问题。成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后进行转型。
建立一个简单的泛型数组包装器。
public class GenericArray<T> {
private T[] array;
public GenericArray(int sz) {
this.array = (T[]) new Object[sz];
}
public void put(int index,T item){
array[index] = item;
}
public T get(int index){return array[index];}
public T[] rep(){return array;}
public static void main(String[] args) {
GenericArray<Integer> integerGenericArray = new GenericArray<>(10);
//java.lang.ClassCastException
// Integer[] ia = integerGenericArray.rep();
Object[] rep = integerGenericArray.rep();
}
}
因为我们不能声明T[] array = new T[sz],隐藏我们创建了一个对象数组,然后转型。
rep方法返回T[],在main中用于integerGenericArray,应该返回Integer[],但是如果调用,并尝试用Integer[]来捕获结果会得到ClassCastException,这是因为实际的运行时类型是Object[].
Thinking In Java Part10(泛型擦除)
最新推荐文章于 2020-06-16 16:52:31 发布