当类中没有定义泛型类型变量时,处理泛型类成员

时间:2016-10-19 07:13:28

标签: java generics

请查看以下代码:

public class MyClass<T extends MyObject> {
    private Class<T> clazz;
    public void setClass1(Class<T> clazz) {
        this.clazz = clazz;
    }
    public Class<T> getClass1() {
        return clazz;
    }
    public T getObject1() {
        T o = null;
        try {
            o = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return o;
    }
}

以上工作原理很好,但要求在创建任何T对象之前知道类类型MyClass。现在,如果我要在T被调用之前延迟指定setClass1(),我必须将<T extends MyObject>声明中的MyClass删除为以下内容:

public class MyClass {
//  private Class<T> clazz; // doesn't work anymore as T is unknown, so do the following instead
    private Class<? extends MyObject> clazz;
    public <T extends MyObject> void setClass1(Class<T> clazz) {
        this.clazz = clazz; // OK
    }
    public void setClass2(Class<? extends MyObject> clazz) {
        this.clazz = clazz; // OK
    }
    public <T extends MyObject> Class<T> getClass1() {
        return clazz; // Error, need casting to Class<T>
    }
    public Class<? extends MyObject> getClass2() {
        return clazz; // OK
    }
    public <T extends MyObject> T getObject1() {
        T o = null;
        try {
            o = clazz.newInstance(); // Error, must cast to T
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return o;
    }
    public MyObject getObject2() {
        MyObject o = null;
        try {
            o = clazz.asSubclass(MyObject.class).newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return o; // OK, but lost the difference between ? extends MyObject and MyObject
    }
}

首先,为什么getClass1()会出错,而setClass1()还可以?

其次,为什么getObject1()中的返回必须转换为(T)

第三,如何在没有不必要的投射和getObject1()的情况下修复@SuppressWarnings("unchecked")

第四,在我的情况下会更改为使用setClass1()getClass1()getObject1()更改为setClass2()getClass2()getObject2()?< / p>

2 个答案:

答案 0 :(得分:2)

  

首先,为什么getClass1()给出错误,而setClass1()是否正常?

setClass()中,您将Class<? extends MyObject>分配给Class<? extends MyObject>类型的变量。这是允许的。

getClass()中,您希望从Class<T>类型的变量中检索Class<? extends MyObject>。但编译器无法知道变量中存储的内容是{1 {}还是Class<T>。所以这是禁止的。

  

其次,为什么必须将getObject1()中的返回强制转换为(T)?

Class<U>返回Class<? extends MyObject>.newInstance()类型的对象。这与? extends MyObject不同。这就是你必须施展的原因。

  

第三,如何修复getObject1()而不进行不必要的转换或@SuppressWarnings(“unchecked”)?

如上所述,上面的版本不包含不必要的强制转换。您要么必须使T成为类本身的通用参数,要么需要强制转换。

答案 1 :(得分:1)

private Class<? extends MyObject> clazz;

public <T extends MyObject> void setClass1(Class<T> clazz) {
    this.clazz = clazz; // OK
}

在此示例中,clazz被定义为可以包含任何类的Class对象的引用,这些Class对象是MyObject的子类的某种未知类型。< / p>

任何部分很重要。这就是setClass1有效的原因,因为它有效地为clazz - MyObject的子类型分配了相同的内容。因此,编译器不会抱怨。

您的方法setClass1实际上是此方法的更通用版本:

public void setClass3(Class<MyObject> clazz) {
    this.clazz = clazz; // OK
}

因为setClass3将最具体的类型Class<MyObject>分配给clazz,它可以容纳任何MyObject或其子类

继续前进。

  public <T extends MyObject> Class<T> getClass1() {
        return clazz; // Error, need casting to Class<T>
  }

这不起作用,因为TMyObject的子类,但您可以确定clazz持有Class<MyObject>Class<T>的某些值T'

例如,假设您还在某处定义了这些类:

public class MySubObject1 extends MyObject {...}

public class MySubObject2 extends MyObject {...}

想象一下,通配符类型?代表MySubObject1T代表MySubObject2,由于它们彼此没有关系,编译器会抱怨。请记住,你不能这样做:

    MySubObject1 so1 = new MySubObject2(); // compiler error

    MySubObject2 so2 = new MySubObject1(); // compiler error

getObject1中存在类似的问题 - 编译器无法知道T表示clazz的类或超类(记住它可以包含MyObject的任何子类) ,这就是为什么铸造是必要的。

使用您当前的设计,您无法真正避免在@SuppressWarnings("unchecked")中进行投射和getObject1。如果可能,请尽量避免混合泛型类型和通配符。

还有一件事。 getClass2迫使用户处理通配符 - 这不是一个很好的API设计决策。

public class MyClass课程中的方法存在潜在的类型安全问题。

您确定原来的public class MyClass<T extends MyObject>课程不能满足您的需求吗?它看起来很好,除了clazz.newInstance();部分,因为它需要MyObject并且它的每个子类都有一个零参数构造函数。