简体   繁体   English

是什么导致了这个“未经检查的演员”警告?

[英]What causes this “unchecked cast” warning?

In java, i have made (and am using without issues), this method:在 java 中,我已经做了(并且正在使用没有问题),这个方法:

private final HashMap<String, Object> data = new HashMap<>();

    public final @NotNull <clazz> Collection<clazz> getCollection(@NotNull String key, @NotNull Object clazz) {
        Object object = this.data.get(key);
        if (object instanceof Collection) {
            Collection<?> collection = (Collection<?>) object;
            for (Object o : collection) {
                if (o.getClass().equals(clazz)) {
                    return (Collection<clazz>) collection;
                }
                break;
            }
        }
        return Collections.emptyList();
    }

Specifically, the line return (Collection<clazz>) collection;具体来说,行return (Collection<clazz>) collection; is producing the warning:正在产生警告:

  • Unchecked cast: 'java.util.Collection<capture<?>>' to 'java.util.Collection<clazz>'

Honesty where appropriate: i improvised this method out of neccessity about one year ago, shortly after i started learning to code.在适当的情况下诚实:大约一年前,在我开始学习编码后不久,我出于需要即兴创作了这种方法。 Unfortunately, even now, i still do not understand what exactly the <clazz> stuff i put there is doing, why it works and how it works.不幸的是,即使是现在,我仍然不明白我放在那里的<clazz>究竟是做什么的,它为什么起作用以及它是如何起作用的。

In that sense: i'm looking to understand - not to be spoonfed the solution to remove the warning message, of course.从这个意义上说:我希望了解 - 当然,不要被提供删除警告信息的解决方案。 (And i totally didn't use @SuppressWarnings("unchecked") prior to this post.) (而且我在这篇文章之前完全没有使用@SuppressWarnings("unchecked") 。)

/edit: /编辑:

After reading the replies so far, i am in the ball park of understand why this warning is produced.在阅读了到目前为止的回复之后,我正在理解为什么会产生这个警告。

Now, for a theoretical workaround that should solve the problem:现在,对于应该解决问题的理论解决方法:

    public final @NotNull <T> Collection<T> getCollection(@NotNull String key, @NotNull Class<T> dataType) {
        Object object = this.data.get(key);
        Collection<T> r = new ArrayList<>();
        if (object instanceof Collection) {
            Collection<?> collection = (Collection<?>) object;
            for (Object o : collection) {
                if (o.getClass().equals(dataType)) {
                    r.add((T) o);
                }
            }
        }
        return r;
    }

But now the line r.add((T) o);但现在行r.add((T) o); is producing the same warning.正在产生相同的警告。 There must be a better condition other than o.getClass().equals(dataType) that will make the IDE understand.除了o.getClass().equals(dataType)之外,必须有更好的条件才能让 IDE 理解。 (IntelliJ Community Edition, by the way) (顺便说一下,IntelliJ 社区版)

There are two clazz variables here: the first is the second parameter to the method, and the other is the type parameter <clazz> of the method.这里有两个clazz变量:第一个是方法的第二个参数,另一个是方法的类型参数<clazz> I strongly advise to rename the second to avoid this ambiguity (normally type parameters are named with a single uppercase letter)`.我强烈建议重命名第二个以避免这种歧义(通常类型参数以单个大写字母命名)`。

The reason the compiler is showing this warning is that it cannot guarantee (ie check at compile time) that the cast is safe.编译器显示此警告的原因是它不能保证(即在编译时检查)强制转换是安全的。 Nothing guarantees that the type of the parameter which you are doing the check against the element type is the same as the type parameter.没有任何东西可以保证您对元素类型进行检查的参数类型与类型参数相同。 So you can possible have an Integer passed as the argument, while you are returning a Collection<String> by calling the method with a String type argument:因此,您可以将Integer作为参数传递,同时通过使用String类型参数调用方法返回Collection<String>

data.put("a", List.of(1, 2));
    
Collection<String> collection = getCollection("a", Integer.class);
String first = collection.stream().findAny().get();  // ClassCastException at runtime

You can slightly improve that by having the class use the same type parameter:您可以通过让 class 使用相同的类型参数来稍微改进一下:

public final @NotNull <T> Collection<T> getCollection(@NotNull String key, @NotNull Class<T> clazz) {
    Collection<?> object = data.get(key);
    if (object instanceof Collection) {
        Collection<?> collection = (Collection<?>) object;
        for (Object o : collection) {
            if (o.getClass().equals(clazz)) {
                return (Collection<T>) collection;
            }
            break;
        }
    }
    return Collections.emptyList();
}

But still this warning will stay because it's up to you to guarantee that all the collection elements are of the type T at runtime:但是这个警告仍然会保留,因为您需要保证所有集合元素在运行时都是T类型:

data.put("a", List.of("x", 2));
    
Collection<String> collection = SOTest.getCollection("a", String.class);
for(String s : collection) {
     System.out.println(s);  // second element will still cause ClassCastException
}

In addition to the (correct) observations in M Anouti's answer :除了M Anouti 回答中的(正确)观察结果:

You are only checking whether the type of the first member matches your expectations.您只是在检查第一个成员的类型是否符合您的期望。 This is not enough to determine the type of the collection in general.这通常不足以确定集合的类型。 For example:例如:

public static void main(String[] args) {

    Main m = new Main();        
    List<Number> numbers = List.of(1, 0.0, BigInteger.valueOf(4));
    m.data.put("nums", numbers);
            
    Collection<Integer> coll = m.getCollection("nums", Integer.class);
    List<Integer> output = new ArrayList<>(coll);
    System.out.println(output);
    output.sort(Integer::compare);
}

Output: Output:

[1, 0.0, 4]
Exception in thread "main" java.lang.ClassCastException: class java.lang.Double cannot be cast to class java.lang.Integer (java.lang.Double and java.lang.Integer are in module java.base of loader 'bootstrap')
    at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
    at java.base/java.util.TimSort.sort(TimSort.java:220)
    at java.base/java.util.Arrays.sort(Arrays.java:1306)
    at java.base/java.util.ArrayList.sort(ArrayList.java:1721)
    at world.Main.main(Main.java:23)

As you can see, the unsafe cast leads to an incorrect generic type in the result, and this is only detected at runtime when it is actually attempted to use the members as instances of the declared type.如您所见,不安全强制转换导致结果中的泛型类型不正确,并且仅在运行时实际尝试将成员用作声明类型的实例时才检测到。 In this example, the println accepts the incorrect type without exception, because that method does not care - it just invokes toString , and every Object has that.在此示例中, println无一例外地接受不正确的类型,因为该方法不关心 - 它只是调用toString ,并且每个 Object 都有。 Only when we try to invoke Integer.compare , this fails.只有当我们尝试调用Integer.compare时,才会失败。

Even if you check all elements, just because a collection currently does not contain an element of a given type does not mean it never will - if the collection is actually a Collection<Number> , but currently only contains Double s, this does not mean that it can be safely used as a Collection<Double> .即使您检查所有元素,仅仅因为集合当前不包含给定类型的元素并不意味着它永远不会 - 如果集合实际上是Collection<Number> ,但当前仅包含Double s,这并不意味着它可以安全地用作Collection<Double>

Testing o.getClass().equals(clazz) doesn't mean that it's a Collection<clazz> .测试o.getClass().equals(clazz)并不意味着它是Collection<clazz>

Collection<clazz> can be thought of as the intersection of two types: Collection<clazz>可以被认为是两种类型的交集:

  • Collection<? extends clazz> Collection<? extends clazz> is a Collection in which all elements are instances of clazz , or are null. Collection<? extends clazz>是一个Collection ,其中所有元素都是clazz的实例,或者是 null。
  • Collection<? super clazz> Collection<? super clazz> is a Collection to which it is acceptable to add elements of clazz , or null. Collection<? super clazz>是一个Collection ,可以向其中添加clazz或 null 的元素。

All you are determining in testing that the first element is an instance of clazz is that it is acceptable to have added an instance of clazz to that Collection : that is, you can only determine that it's an instance of Collection<? super clazz>在测试第一个元素是clazz的实例时,您所确定的只是将clazz的实例添加到该Collection是可以接受的:也就是说,您只能确定它是Collection<? super clazz> Collection<? super clazz> , because the presence of an instance of clazz indicates that it would be safe to add another instance of clazz . Collection<? super clazz> ,因为clazz实例的存在表明添加另一个clazz实例是安全的。

And the fact you're only checking the first element is irrelevant: you can check all of the elements of the list, and still you don't know any more information: all you can determine is that at the time you tested the elements , there were no instances of another class in the collection.而且您只检查第一个元素的事实是无关紧要的:您可以检查列表中的所有元素,但您仍然不知道更多信息:您所能确定的是在您测试元素时,集合中没有另一个 class 的实例。

There are, in general, no guarantees that will remain true over time.一般来说,没有任何保证会随着时间的推移而保持不变。 If some other part of your code has access to the same Collection instance, but this has additional type information which permit instances of a more general type to be added to the Collection , this can break the type-safety of the value returned by getCollection .如果您的代码的其他部分可以访问同一个Collection实例,但是这具有允许将更通用类型的实例添加到Collection的附加类型信息,这可能会破坏getCollection返回值的类型安全性。

To give a practical example:举一个实际的例子:

List<Object> list = new ArrayList<>();
list.add(0);
list.add(1);

// This is the class in the question. I don't know the API for
// adding stuff to it, so I'm just making it up.
YourClass yourClass = new YourClass();
yourClass.put("key", list);

// Invoke the method in the question to get an unsafe collection.
Collection<Integer> unsafeCollection = yourClass.getCollection("key", Integer.class);

// This affects the contents of unsafeCollection.
list.clear();
list.add("hello");

// Now you will get a ClassCastException, because list (and collection,
// which is the same instance as list) only contains Strings.
for (Integer i : unsafeCollection) {}

However, if you had declared your method to return a Collection<? super Integer>但是,如果您已声明您的方法返回Collection<? super Integer> Collection<? super Integer> : Collection<? super Integer>

Collection<? super Integer> safeCollection = yourClass.getCollectionSafe("key", Integer.class);

You wouldn't be able to iterate this assuming an Integer element type:假设Integer元素类型,您将无法迭代:

for (Integer i : safeCollection) {}  // Compiler error.

But you could for Object element type:但是您可以为Object元素类型:

for (Object o : safeCollection) {}  // Fine.

So it wouldn't matter if list.add("hello") had been invoked.因此,是否调用了list.add("hello")并不重要。


But now the line r.add((T) o);但现在行r.add((T) o); is producing the same warning正在产生相同的警告

It is important to understand what unchecked warnings are actually telling you: the compiler is unable to insert any instruction to determine if that the cast on that line is correct or not (as opposed to a checked cast, which would result in a checkcast bytecode instruction).了解未经检查的警告实际上告诉您什么很重要:编译器无法插入任何指令来确定该行上的转换是否正确(与检查转换相反,这将导致检查checkcast字节码指令)。 A checkcast needs to know the type it's checking against, statically; checkcast需要静态地知道它正在检查的类型; it doesn't know what T is here because it's a type variable.它不知道T在这里是什么,因为它是一个类型变量。

You are given the ability to tell the compiler, via @SuppressWarnings : "trust me, I know this is safe";您可以通过@SuppressWarnings告诉编译器:“相信我,我知道这是安全的”; but in doing so, you are assuming responsibility for ensuring that it is.但在这样做的过程中,你承担了确保它是的责任。 In that case, you can be sure it is safe, because of the immediately preceding check, so suppression is appropriate.在这种情况下,您可以确定它是安全的,因为前面的检查,所以抑制是合适的。

You might consider rewriting with a local variable, in order to be able to apply the suppression to only that cast, rather than having to apply it to the whole method:您可能会考虑使用局部变量重写,以便能够将抑制应用于该强制转换,而不必将其应用于整个方法:

if (o.getClass().equals(dataType)) {
  @SuppressWarnings("unchecked")  // Safe, because of preceding check.
  T castO = (T) o;
  r.add(castO);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM