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

发布于 2024-10-10 04:28:00 字数 898 浏览 0 评论 0原文

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

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

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);

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

I have a List that contains a certain superclass (like Vehicle), and I would like to write a method that returns the objects in that list that are instances of a certain subclass (like Car).

So far I have this, but it generates a typical "unchecked" operation compiler warning:

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.

I'm ok with any other method of accomplishing this (I couldn't find anything in Collections, but it's possible some JDK method can do it), but ideally it would provide the following interface:

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

I would like to know why I was receiving a compiler warning on the original code, though.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

抱着落日 2024-10-17 04:28:00

您收到警告是因为如果不理解 isAssignableFrom() 的含义,编译器(或 IDE)就无法知道强制转换是否安全。但 isAssignableFrom() 不是一种语言功能,它只是一个库方法。就编译器而言,这与您所说的一样,

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

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

You're getting a warning because there's no way for the compiler (or the IDE) to know that the cast is safe, without understanding the meaning of isAssignableFrom(). But isAssignableFrom() isn't a language feature, it's just a library method. As far as the compiler's concerned, it's the same as if you'd said

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

However, you know what isAssignableFrom() means, so you know it's safe. This is exactly the sort of situation @SuppressWarnings is meant for.

橙幽之幻 2024-10-17 04:28:00

这个怎么样?

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));
}

How about this?

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

Does the compiler still complain like that?


But if I were you I'd use Guava, that will make your method a one-liner:

public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
    return Lists.newArrayList(Iterables.filter(getVehicles(), type));
}
等风来 2024-10-17 04:28:00

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

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

基本上,编译器会说:“我无法确认这是否安全,因此您需要确保自己知道自己在做什么。”您的代码没有损坏,您只需要小心谨慎即可。

The problem is that the compiler isn't smart enough to know that vehicle is of class "type". This is a runtime check and the compiler doesn't do that kind of analysis. There are lots of situations like this. For example I use if (true) return; to exit out of a function early during debugging all the time. If I use just return, the compiler realizes that there is unreachable code, but with the conditional, the compiler doesn't realize that it's impossible to get into that branch.

Consider if you replace your conditional with if (false) {. The code has no chance of throwing an exception but still contains an unsafe cast.

Basically the compiler is saying, "I can't confirm that this is safe so it's up to you to make sure you know what you are doing." Your code isn't broken, you just need to exercise caution.

蓝礼 2024-10-17 04:28:00

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)

2 things:

I would like to know why I was
receiving a compiler warning on the
original code, though.

1st:
Look at the code of Class: it does hide the cast for you. Normally casting to any arbitrary type (T) should be a warning but Class.cast actually checks and ignores the compiler warning(nonsense).

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

2nd
That being said: Generics warnings is the 1st thing to disable. Having mixture of old code and now just doesn't worth suppress warning, nor I'd care. I am just waiting to see how generics reduce ClassCastException and it's probably true in only one case: using add instead addAll (put/putAll)

只是我以为 2024-10-17 04:28:00

您可能无法在方法中添加 @SuppressWarning("unchecked")

You might be stuck with adding @SuppressWarning("unchecked") to the method

著墨染雨君画夕 2024-10-17 04:28:00

您正在对 Vehicle 进行操作,这是一个超类,并且您尝试向下转换为 Vehicle 的子类。这始终是不安全的强制转换,并且会给您带来编译器警告。

因此,虽然您可以在没有警告的情况下从 Car 转换为 Vehicle(因为 Car 扩展了 Vehicle),但编译器无法知道变量类型因为 Vehicle 实际上是一辆汽车。

通过使用强制转换(通过使用 (Car)cast(..)),您可以告诉编译器您了解得更多。
编译器讨厌人类,但仍然向你发出警告:)

You are operating on Vehicle, which is a superclass and you try to downcast to a subclass of Vehicle. This is always an unsafe cast and will earn you a compiler warning.

So while you can cast from Car to Vehicle without warning (since Car extends Vehicle), the compiler has no way of knowing that the variable typed as Vehicle is actually a car.

By using a cast (either by using (Car) or cast(..)), you are telling the compiler that you know better.
The compiler hates people and still hands out a warning to you :)

_畞蕅 2024-10-17 04:28:00

就我个人而言,我认为您采取了错误的方法。 Java 中的泛型是仅限编译时的功能。您知道在编译时需要哪种类型的列表,因此只需创建一个返回正确类型列表的辅助方法:

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

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

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

根本没有选角问题。需要编写更多代码,但您也可以创建仅返回蓝色汽车、仅返回红色汽车等的重载版本。

Personally, I think you're taking the wrong approach. Generics in Java are a compile-time only feature. You know what kind of list you need at compile-time so just create a helper method that returns the right kind of list:

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

Add the helper method to the class in question and then in your code just do:

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

No casting issues at all. A bit more code to write, but you could also create overloaded versions that returned only blue cars, only red cars, etc.

凉墨 2024-10-17 04:28:00

此问题的多个答案声称该警告是由于编译器无法静态验证强制转换不会失败这一事实造成的。尽管编译器确实无法验证这一点,但这没有理由发出警告!如果编译器对所有无法静态验证的转换标记警告,则会消除所有有意义的转换,因为当可以静态验证转换时,您通常不需要首先进行转换。换句话说,强制转换操作的全部要点在于它可能在运行时失败,这是使用ClassCastException处理的完美情况。

另一方面,当您执行没有足够的类型信息来在运行时验证转换的转换时,就会出现“未经检查的转换”警告。发生这种情况是因为类型参数在运行时被擦除。假设您将静态类型 List 的内容转换为 List。在运行时,这两种类型的对象仅将 ArrayListLinkedList 类作为其唯一的运行时类型信息,没有类型参数。因此,编译器无法插入任何代码来在运行时验证该对象确实是 VehicleList。因此编译器什么也不做,但会引发“未经检查的强制转换”警告。

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

处理从 ListList 的转换的安全方法是迭代每个元素,将它们转换为 Vehicle (您可以在运行时验证,并且会在明确定义的点引发ClassCastException),并将它们添加到新列表中。我编写了一些通用代码来准确地做到这一点 这个答案

Multiple answers to this question claim that the warning is due to the fact that the compiler can't statically verify that the cast cannot fail. Although the compiler indeed cannot verify that, that's no reason to give a warning! If the compiler would flag warnings for all casts that can't be verified statically, that banishes all meaningful casts, because when a cast can be verified statically, you generally don't need to be doing the cast in the first place. In other words, the entire point of a cast operation is that it can fail at runtime, which is a perfectly fine situation that is handled with a ClassCastException.

The "unchecked cast" warning on the other hand, happens when you do a cast for which there is insufficient type information to verify the cast at runtime. This can happen because of the erasure of type arguments at runtime. Suppose that you cast something of static type List<?> to List<Vehicle>. At runtime, objects of both of these types simply have the class ArrayList or LinkedList as their only runtime type information, without type arguments. So the compiler can't insert any code that will verify at runtime that the object is indeed a List of Vehicle. So the compiler does nothing, but raises an "unchecked cast" warning.

This is a useful warning, because you may run into a problem when you start using the result of the cast. Since the result has static type List<Vehicle>, you may write code that treats elements from the list as a Vehicle, without having to write a cast. But there is actually still a cast at runtime, and it will fail when the List turns out to contain anything that is not a Vehicle. So you may get a ClassCastException at a point where you did not expect it.

The safe way to handle such a conversion from List<?> to List<Vehicle>, is to iterate over each of the elements, cast them to Vehicle (something you can verify at runtime and which will raise a ClassCastException at a well defined point), and add them to a new list. I wrote some generic code to do precisely that in this answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文