需要使用泛型将列表过滤到特定子类

时间:2011-01-05 15:04:47

标签: java generics compiler-warnings

我有一个List包含某个超类(如Vehicle),我想写一个方法,返回该列表中的对象,这些对象是某个子类的实例(如Car)。

到目前为止,我有这个,但它生成了一个典型的“未经检查”的操作编译器警告:

public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
    List<T> result = new ArrayList<T>();

    for (Vehicle vehicle : getVehicles()) {
        if (type.isAssignableFrom(vehicle.getClass())) {
            result.add(type.cast(vehicle)); // Compiler warning here
            // Note, (T)vehicle generates an "Unchecked cast" warning (IDE can see this one)
        }
    }

    return result;
}

Warning: Note: Test.java uses unchecked or unsafe operations.

我可以使用任何其他方法来完成此任务(我在Collections中找不到任何内容,但有些JDK方法可以做到这一点),但理想情况下它会提供以下接口:

List<Car> cars = getVehiclesOfType(Car.class);

我想知道为什么我在原始代码上收到编译器警告。

8 个答案:

答案 0 :(得分:5)

您正在收到警告,因为编译器(或IDE)无法在不了解isAssignableFrom()的含义的情况下知道演员阵容是安全的。但isAssignableFrom()不是语言功能,它只是一种库方法。就编译器而言,它就像你说的一样

    if (type.getName().contains("Elvis")) {
        result.add(type.cast(vehicle));
    }

但是,您知道isAssignableFrom()的含义,因此您知道它是安全的。这正是@SuppressWarnings所针对的情况。

答案 1 :(得分:3)

这个怎么样?

if (type.isInstance(vehicle)) {
    result.add((T)(vehicle));
}

编译器是否仍然抱怨?


但如果我是你,我会使用Guava,这将使你的方法成为一个单行:

public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
    return Lists.newArrayList(Iterables.filter(getVehicles(), type));
}

答案 2 :(得分:1)

问题是编译器不够聪明,不知道车辆属于“类型”类。这是运行时检查,编译器不进行这种分析。有很多这样的情况。例如,我使用if(true)return;在调试过程中一直退出函数。如果我只使用return,编译器会意识到存在无法访问的代码,但是对于条件,编译器没有意识到进入该分支是不可能的。

考虑是否用if(false){替换条件。代码没有机会抛出异常,但仍然包含不安全的演员。

基本上编译器说,“我无法确认这是安全的,所以由你来确保你知道你在做什么。”您的代码没有被破坏,您只需要谨慎行事。

答案 3 :(得分:1)

2件事:

  

我想知道我为什么   收到编译器警告   但原始代码。

第一的 看看Class的代码:它会为你隐藏演员表。通常,转换为任意类型(T)应该是一个警告,但Class.cast实际上会检查并忽略编译器警告(废话)。

public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException();
    return (T) obj;
}

第二 话虽如此:泛型警告是禁用的第一件事。混合旧代码现在只是不值得压制警告,我也不在乎。我只是在等着看泛型是如何减少ClassCastException的,只有一种情况可能是这样:使用add代替addAll(put / putAll)

答案 4 :(得分:0)

您可能会在向方法

添加@SuppressWarning("unchecked")时遇到困难

答案 5 :(得分:0)

您正在使用Vehicle,这是一个超类,您尝试向下转换为Vehicle的子类。这总是一个不安全的演员,会给你一个编译器警告。

因此,虽然您可以在没有警告的情况下从Car转换为Vehicle(从Car extends Vehicle开始),但编译器无法知道键入为Vehicle的变量实际上是一辆车。

通过使用强制转换(使用(Car)cast(..)),您告诉编译器您更了解。 编译器讨厌别人并且仍向你发出警告:)

答案 6 :(得分:0)

就个人而言,我认为你采取了错误的做法。 Java中的泛型是compile-time only feature。您知道在编译时需要什么样的列表,所以只需创建一个返回正确类型列表的帮助方法:

public static List<Car> getCars( List<Vehicle> vlist ){ /* code here */ }

将辅助方法添加到相关类中,然后在代码中执行:

List<Car> cars = Cars.getCars( getVehicles() );

根本没有铸造问题。要写一些代码,但你也可以创建只返回蓝色汽车,只有红色汽车等的重载版本。

答案 7 :(得分:0)

此问题的多个答案声称警告是由于编译器无法静态验证导致演员无法失败的事实。虽然编译器确实无法验证,但没有理由给出警告!如果编译器会为无法静态验证的所有强制转换标记警告,则会消除所有有意义的强制转换,因为当可以静态验证强制转换时,通常不需要首先进行强制转换。换句话说,强制转换操作的整个要点是可以在运行时失败,这是一个用ClassCastException处理的非常好的情况。

另一方面,当您执行类型信息不足以在运行时验证强制转换的强制转换时,会发生“未经检查的强制转换”警告。这可能是因为在运行时擦除了类型参数。假设您将静态类型List<?>转换为List<Vehicle>。在运行时,这两种类型的对象只有类ArrayListLinkedList作为唯一的运行时类型信息,没有类型参数。因此,编译器无法插入任何将在运行时验证该对象确实是List Vehicle的代码。所以编译器什么都不做,但会引发“未经检查的强制转换”警告。

这是一个有用的警告,因为当您开始使用强制转换的结果时可能会遇到问题。由于结果具有静态类型List<Vehicle>,因此您可以编写将列表中的元素视为Vehicle的代码,而无需编写强制转换。但实际上在运行时仍存在一个强制转换,当List结果包含任何不是Vehicle的内容时,它将失败。所以你可能会在你没想到的时候得到ClassCastException

处理从List<?>List<Vehicle>的转换的安全方法是迭代每个元素,将它们转换为Vehicle(你可以在运行时验证,并在定义良好的点上引发ClassCastException,并将它们添加到新列表中。我写了一些通用代码来准确地在this answer中完成。