美文网首页
泛型擦除的补偿

泛型擦除的补偿

作者: 呆呆李宇杰 | 来源:发表于2017-09-14 22:04 被阅读191次

    擦除的补偿

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

    public class Erased<T> {
        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<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> ctt1 = new ClassTypeCapture<>(Building.class);
            System.out.println(ctt1.f(new Building()));
            System.out.println(ctt1.f(new House()));
    
            ClassTypeCapture<House> 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> {
        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> fe = new ClassAsFactory<>(Employee.class);
            System.out.println("ClassAsFactory<Employee> succeeded");
            try {
                ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class);
            } catch (Exception e) {
                System.out.println("ClassAsFactory<Integer> fail");
            }
        }
    }
    
    // Outputs
    ClassAsFactory<Employee> succeeded
    ClassAsFactory<Integer> fail
    

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

    interface FactoryI<T> {
        T create();
    }
    
    class Foo2<T> {
        private T x;
    
        public <F extends FactoryI<T>> Foo2(F factory) {
            x = factory.create();
        }
    }
    
    class IntegerFactory implements FactoryI<Integer> {
    
        @Override
        public Integer create() {
            return new Integer(0);
        }
    }
    
    class Widget{
        public static class Factory implements FactoryI<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;
    
        GenericWithCreate() {
            element = create();
        }
    
        protected abstract T create();
    }
    
    class X {
    }
    
    class Creator extends GenericWithCreate<X> {
    
        @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
    

    相关文章

      网友评论

          本文标题:泛型擦除的补偿

          本文链接:https://www.haomeiwen.com/subject/ygjjsxtx.html