民间, 我有一个基类,比如说:
public class BaseType { private String id; ... }
然后是三个子类:
public class TypeA extends BaseType { ... }
public class TypeB extends BaseType { ... }
public class TypeC extends BaseType { ... }
我有一个容器类来维护这些类型的对象列表:
public class Container
{
private List<TypeA> aList;
private List<TypeB> bList;
private List<TypeC> cList;
// finder method goes here
}
现在我想向容器添加一个finder方法,它将从其中一个列表中找到一个对象。 finder方法编写如下:
public <T extends BaseType> T find( String id, Class<T> clazz )
{
final List<T> collection;
if( clazz == TypeA.class )
{
collection = (List<T>)aList;
}
else if( clazz == TypeB.class )
{
collection = (List<T>)bList;
}
else if( clazz == TypeC.class )
{
collection = (List<T>)cList;
}
else return null;
for( final BaseType value : collection )
{
if( value.getId().equals( id ) )
{
return (T)value;
}
}
return null;
}
我的问题是:如果我在上面的finder中没有将所有演员表添加到T,我会收到编译错误。我认为编译应该能够根据泛型方法()的参数化来推断类型。谁能解释一下呢?
感谢。
-Raj
答案 0 :(得分:3)
在确定作业是否合法时,编译器不会将您的手动类型检查考虑在内。就分析仪而言,它需要编译这一行:
collection = (List<T>)aList;
以便在T
时有效,比如说TypeC
。看起来像是从List<TypeA>
到List<TypeC>
的任务,没有任何强制转换,它必须标记为错误。
答案 1 :(得分:3)
我认为编译器应该能够根据泛型方法的参数化来推断类型。
你所说的是编译器应该看
if( clazz == TypeA.class )
和
Class<T> clazz
并且根据该推断,在if语句的范围内T
确实是TypeA
。
我同意一个聪明的编译器可能能够做到这一点,但不幸的是,当前的Java编译器不够聪明,无法推断出这些信息。
这类似于在成功检查实例后必须进行类型转换
if (a instanceof Something){
((Something)a).someMethod();
}
原因是这个高级功能不经常需要,需要时间来实现(如果实现不正确会很糟糕),并且可能会使编译时间更长。
答案 2 :(得分:2)
private List<TypeA> aList;
private List<TypeB> bList;
public <T extends BaseType> T find(String id, Class<T> clazz) {
final List<? extends BaseType> collection;
if (clazz == TypeA.class) {
collection = aList;
} else if (clazz == TypeB.class) {
collection = bList;
}
for (final BaseType value : collection) {
if (value.getId().equals(id)) {
return clazz.cast(value);
}
}
}
答案 3 :(得分:0)
首先,正确的代码(没有强制转换为T
)将是这样的:
for (final T value : collection) {
if (value.getId().equals(id)) {
return value;
}
}
这是因为您的方法声明返回类型为T
,因此调用者将期望具体的返回类型,如TypeA
或TypeB
。例如:
TypeA find("id", TypeA.class);
但是如果将值类型概括为BaseType
,那么这可能与具体类型不匹配。因为,正如您所知,TypeA
也是BaseType
,但BaseType
不一定是TypeA
。
因此java编译器会抱怨它无法将BaseType
转换为具体类型。要解决此问题,您需要将值类型保留为T
而不进行概括。我知道,BaseType
和T
听起来不是非常不同的类型,但这就是javac的工作原理。
类似的想法适用于列表转换。由于List<T> collection
可能具有任何具体类型,因此编译器无法确保它在编译时具有任何具体类型,如List<TypeA>
。因为泛型背后的想法是在编译时是安全的,它会给你一个编译错误(除非你用强制转换强制它)。
答案 4 :(得分:0)
我建议放弃反射人工制品,并使用标准策略。遗憾的是,不能使用通用枚举(虽然它们可以实现提供实际泛型参数的泛型类型)。
private List<TypeA> aList;
private List<TypeB> bList;
private List<TypeC> cList;
public abstract class ListType<T extends BaseType> {
/*private*/ abstract T get(Container container); // private isn't virtual.
// Add extras to taste.
}
public static final ListType<TypeA> LIST_A = bew ListType<TypeA>() {
/*private*/ ListType<TypeA> get(Container container) { return container.aList; }
};
public static final ListType<TypeB> LIST_B = bew ListType<TypeB>() {
/*private*/ ListType<TypeB> get(Container container) { return container.bList; }
};
public static final ListType<TypeC> LIST_C = bew ListType<TypeC>() {
/*private*/ ListType<TypeC> get(Container container) { return container.cList; }
};
public <T extends BaseType> T find(String id, ListType<T> type) {
final List<T> collection = type.get(this);
for (final T value : collection) {
if (value.getId().equals(id)) {
return value;
}
}
return null;
}