4

Why does using the cast method on the Class<?> Class produce an unchecked warning at compile time?

If you peek inside the cast method, you found this code :

public T cast(Object obj) 
{
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj; // you can see there is a generic cast performed here
}

If i do a generic cast, the compiler complains saying that there is unchecked warning.


Additional background information

You could find an example of how I arrived at this question inside the Book Effective Java 2 edition, page 166 (of the pdf).

The author writes this code

public <T> T getFavorite(Class<T> type) 
{
    return type.cast(favorites.get(type));
}

vs

public <T> T getFavorite(Class<T> type) 
{
    return (T) (favorites.get(type));
}

I just don't get the difference and why the compiler complains about the unchecked warning. In the end, both pieces of code do an explicit cast (T) object, don't they?.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Victor
  • 3,841
  • 2
  • 37
  • 63

2 Answers2

7

Without a @SuppressWarnings("unchecked") annotation, the source code of java.lang.Class would produce warnings during compilation. Warnings are not produced because the JDK classes are located in a library that is already compiled. I am sure compiling the JDK yourself would produce some warnings.

The comment by @Bohemian that this is an 'official kludge' is essentially correct and there are many examples of code like this. (Another example is java.lang.Enum#getDeclaringClass.) It is safe to use because the logic as it is written is correct and it exists so you don't have to write this kind of ugly stuff yourself.

My advice is not to think too much about this implementation: what matters is that java.lang.Class#cast conforms to the semantics of a checked cast.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • Ah, I suspected as much when you said that the `@SuppressWarnings` annotation didn't exist on `cast()` earlier. Wasn't sure, though, but in any case, you're more correct than me. – awksp May 26 '14 at 05:25
  • So, there is an implicit suppress warning? @user3580294 – Victor May 26 '14 at 05:36
  • @Victor No, no warning is ever generated because your compiler never compiles the source code. I made a mistake with my answer, which is why I removed it. – awksp May 26 '14 at 05:36
  • I see. Although is tricky.. very. So i shall leave this as correct answer? (is too tale here, i have to go to sleep, tomorrow i'll take a decision!) Thanks!!!! – Victor May 26 '14 at 05:41
  • 1
    @Victor From what I know, this is correct. To try to provide a bad analogy, it's like writing an encyclopedia whose wording is somewhat questionable (e.g. contains unchecked casts). If you gave your editor (the compiler), the draft (source code), your editor (compiler) might complain to you that something could be wrong (emits a warning) before sending the draft (code) to the printer, which produces the final product (bytecode). But if the printer is given the finished product (bytecode) to include directly (`import ____`), the editor (compiler) is never involved, so a warning isn't generated. – awksp May 26 '14 at 05:47
1

To enforce memory safety, Java ensures that a variable of reference type actually contains a reference to an object of that type (or a subtype thereof). A cast instruction could violate this invariant, i.e. we can write:

Object o = new Integer(42);
String s = (String) o;  // compiles, but throws ClassCastException at runtime

To prevent this, a cast instruction will check the type of the object referred to, and throw a ClassCastException if it is not.

Prior to the introduction of generics into the Java language, the above held for all casts. However, generics being implemented with type erasure, the runtime does not know which class a type parameter stands for, and therefore can not perform this check if a type parameter is involved.

And that's why the spec distinguishes between a checked cast (which will only succeed at runtime if it is type correct), and an unchecked cast (which might succeed even if not type correct, resulting in heap pollution, and likely a type error at a later time). For instance:

class C<T> {
    final T field;

    C(Object o) {
        this.field = (T) o; // unchecked. Will never throw a ClassCastException.
    }
}

boolean test() {
    C<String> c = new C<String>(42);
    return c.field.startsWith("hello"); // throws ClassCastException, even though there is no cast in the source code at this line!
}

That is, unchecked casts can be unsafe, and should be avoided.

With that background, it is easy to see that a reflective cast (the Class.cast() method) is checked, because they actually implement the check in the method itself:

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

And that safety net is why a reflective cast is preferred to an ordinary cast when the cast involves a type parameter.

meriton
  • 68,356
  • 14
  • 108
  • 175
  • i agree that the Class.cast() method performs a check at runtime because you are passing information, at runtime, about the desired class to cast. But i have two observation, the first code snippet doesn't compile as ``String s = o`` is a narrowing conversion and therebefore the thing referenced by o is not 100% an String compatible object. And about the second snipped the classCastException occurs in the MagicCast invocation, that is what the jvm says at runtime ``java.lang.Integer cannot be cast to java.lang.String`` – Victor May 26 '14 at 05:34
  • 1
    Right about the first example, I fixed it. The second example did throw, but if you look at the stack trace, not in magicCast, but in the caller. I modified the second example to show that heap pollution may not be immediately detected by a caller. – meriton May 26 '14 at 06:01
  • Right, i see, is a very good explanation that you leave here. But at the end, when the "compiler" see ´´return (T) obj;´´ we all guest that it must complain following the Java lenguaje spec. – Victor May 26 '14 at 13:53
  • 1
    Well, you asked why *using* the cast method does not result in a compilation error. If you want to know the cast method can be *compiled* without warnings, you should have asked a different question. – meriton May 26 '14 at 15:20
  • Thanks for the correction!. About the question, i guess, one thing lead to another. Personally i think that both answer are correct and guides towards to an valid answer. I would mark it a correct too, but the cruel point system of stackoverlow doesn't allow me to do that. Personally, i am very grateful with your intention, as with all the other users that kindly collaborate in the answer. – Victor May 26 '14 at 15:37
  • Your example of the C class is very useful as you can watch at runtime (using the debugger and the inspector), assuming that at runtime the field ´field´ will be of type Object, you see that is storing at runtime an object of type Integer. The generic cast at the cosntructor, as all the other generics cast, are at runtime cast to Object, so there is no need to worry about. But when you try to manipulate it as a String, an error arises. The proper thing to do is to change the parameter of the constructor to be of type T, so the check at compile restrict to use bad type. – Victor May 26 '14 at 15:39