泛型擦除的补偿

擦除的补偿

泛型的擦除丢失了在泛型代码中执行某些操作的能力,所以,在运行时任何需要早知道确切类型信息的操作都将无法工作。

public class Erased {
    private  final int SIZE = 100;
    public  void f(Object arg){
        // if (arg instanceof T){}          // Error
        // T var = new T();                 // Error
        // T[] array = new T[SIZE];         // Error
        T[] array = (T[]) new Object[SIZE]; //Unchecked warning
    }
}

虽然偶尔可以绕过这些问题来编程,但是有时必须引入类型标签来对擦除进行补偿。这就意味着需要显示地传递Class对象,以便可以在类型表达式中使用它。

isInstance的补偿

在前面的例子中,对instanceof进行使用但是最终还是失败了,因为其类型信息已经被擦除。但如果引入类型标签,就可以转而使用动态的isInstance()

class Building {
}

class House extends Building {
}

public class ClassTypeCapture {
    Class kind;

    public ClassTypeCapture(Class kind) {
        this.kind = kind;
    }

    public boolean f(Object arg) {
        return kind.isInstance(arg);
    }

    public static void main(String[] args) {
        ClassTypeCapture ctt1 = new ClassTypeCapture<>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));

        ClassTypeCapture ctt2 = new ClassTypeCapture<>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }
}
// Outputs
true
true
false
true

上面的代码中,编译器将确保类型标签可以匹配泛型参数。

创建类型实例

在之前的代码中,创建一个new T()的尝试将无法实现,其部分原因是因为擦除,另一部分原因是因为编译器不能验证T是否含有一个默认(无参)构造器。
Java中的解决方法是传递一个工厂对象,并使用它来创建一个新的实例。而最便捷的工厂对象就是Class对象,因此如果使用类型标签,那么就可以使用newInstance()来创建这个类型的新对象。

class ClassAsFactory {
    T x;

    public ClassAsFactory(Class kind) {
        try {
            x = kind.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

class Employee {
}

public class InstantiateGenericType {
    public static void main(String[] args) {
        ClassAsFactory fe = new ClassAsFactory<>(Employee.class);
        System.out.println("ClassAsFactory succeeded");
        try {
            ClassAsFactory fi = new ClassAsFactory<>(Integer.class);
        } catch (Exception e) {
            System.out.println("ClassAsFactory fail");
        }
    }
}
// Outputs
ClassAsFactory succeeded
ClassAsFactory fail

上面的代码可以通过编译,但是ClassAsFactory会出现异常而失败,因为Integer没有任何的默认构造器。而这个错误不是在编译期捕获的,所以一般不赞成使用这样的方法。而是通过使用显示的工厂,并将限制其类型,是的只能接受实现了这个工厂方法的类。

interface FactoryI {
    T create();
}

class Foo2 {
    private T x;

    public > Foo2(F factory) {
        x = factory.create();
    }
}

class IntegerFactory implements FactoryI {

    @Override
    public Integer create() {
        return new Integer(0);
    }
}

class Widget{
    public static class Factory implements FactoryI{
        @Override
        public Widget create() {
            return new Widget();
        }
    }
}

public class FactoryConstraint {
    public static void main(String[] args) {
        new Foo2(new IntegerFactory());
        new Foo2(new Widget.Factory());
    }
}

上面的代码中通过工厂对象来创键对象,需要注意的是。这是Class的一种变体。这两种方式其实都传递了工厂对象,而Class其实也是内建的工厂对象,上面的方法创建了一个显式的工厂对象,但是却可以获得编译期检查。
另一种补偿的方式是模板方法设计模式,在下面的代码中,get()是模板方法,而create()是在子类中定义的,用来产生子类型的对象。

abstract class GenericWithCreate {
    final T element;

    GenericWithCreate() {
        element = create();
    }

    protected abstract T create();
}

class X {
}

class Creator extends GenericWithCreate {

    @Override
    protected 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();
    }
}
// Outputs
X

你可能感兴趣的:(泛型擦除的补偿)