请查看以下代码:
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>
答案 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>
}
这不起作用,因为T
是MyObject
的子类,但您可以确定clazz
持有Class<MyObject>
或Class<T>
的某些值T'
例如,假设您还在某处定义了这些类:
public class MySubObject1 extends MyObject {...}
和
public class MySubObject2 extends MyObject {...}
想象一下,通配符类型?
代表MySubObject1
而T
代表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
并且它的每个子类都有一个零参数构造函数。