帮助我理解这些通用方法警告

时间:2010-12-23 01:27:48

标签: java generics

民间, 我有一个基类,比如说:

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

5 个答案:

答案 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,因此调用者将期望具体的返回类型,如TypeATypeB。例如:

TypeA find("id", TypeA.class);

但是如果将值类型概括为BaseType,那么这可能与具体类型不匹配。因为,正如您所知,TypeA也是BaseType,但BaseType不一定是TypeA

因此java编译器会抱怨它无法将BaseType转换为具体类型。要解决此问题,您需要将值类型保留为T而不进行概括。我知道,BaseTypeT听起来不是非常不同的类型,但这就是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;

}