ClassCast error: Java 7 vs Java 8
Asked Answered
K

1

30

Is this a bug or feature? The following code runs fine in Java 7 but throws an exception in Java 8:

The last command throws a ClassCast exception in Java8, all the "equivalent" commands above work the same way.

The problem, I think, is that in Java 8, the compiler decides to use String.value(char[]) on the last line instead of String.value(Object) as in Java 7. I would think this should behave the same way for backward compatibility. Am I missing something?

Note: As Marko suggested this is probably related to target type inference introduced in Java 8.

public class Test {
    public static void main(String[] args) {
        System.out.println( getVal().getClass());  // String

        System.out.println( String.valueOf(Test.<Object>getVal()) );   // "abc"

        Object obj = getVal();
        System.out.println( String.valueOf(obj) );  // "abc"

        System.out.println( String.valueOf(getVal()) ); // 7: "abc", 8: Exception 
    }

    // returns a string for simplicity; imagine that given a field, it fetches values from a database
    @SuppressWarnings("unchecked")
    public static <T> T getVal() {
        return (T) "abc";
    }
}

Result in Java 7:

class java.lang.String
abc
abc
abc

Result in Java 8:

class java.lang.String
abc
abc
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to [C
    at Test.main(Test.java:11)

(Note: [C is an array of chars)

Both Java's are on windows:

java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) Client VM (build 24.45-b08, mixed mode, sharing)

java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
Khiva answered 18/4, 2014 at 14:11 Comment(3)
Seems like a bug to me.Traceetracer
Cannot be cast to [C are you missing something in the copy/paste?Transmit
@Rogue: Looks like the notation for an array of chars to me.Disfigurement
G
23

String.valueOf is a heavily overloaded method and you are using it in a context where the argument type must be inferred from the context. On the other hand, the rules of type inference have received a significant overhaul in Java 8; most notably target type inference has been much improved. So, whereas before Java 8 the method argument site did not receive any inference, defaulting to Object in your case, in Java 8 the most specific applicable type was inferred, in this case char[].

However, keep in mind that in both cases the idiom you used is essentially broken so the change in compiler output should perhaps be designated as a "pitfall", but not a "bug".

The unchecked cast is unfortunately sometimes unavoidable, but I can't think of any case where it makes sense to infer the type itself (as opposed to a type parameter) of something which is not created reflectively from a Class object. Therefore you are not likely to actually find yourself in the position shown here, where you infer the type based on the acceptable argument types on the call site. Moreover, it is certainly broken to do this with an overloaded method, leaving the choice of argument type to inference. This can only work "by accident".

Guibert answered 18/4, 2014 at 14:17 Comment(26)
While it makes little sense to do what the example code is doing, if this reflects a Java bug, the bug may affect code that does make sense, or perhaps legacy code that doesn't make sense but needs to be maintained anyway. It's definitely worth figuring out what causes the difference.Disfigurement
I would still expect it to work the same way in Java 7 and 8. Maybe a (unintended?) consequence of the improved generics?Khiva
@Jirka-x1 Yes, this is happening because of the improved target type inference that Java 8 introduced.Guanaco
Cast it to string first @Jirka-x1. That should solve the overload issue. I do think that Overload is the problem.Semipermeable
Neither one nor the other inference seems like the obvious "right one", so I wouldn't at all rush into labeling this as a bug. If legacy code relies on such incidental inference, it should very well be fixed.Guibert
Agreed. IMHO the only bug here is (T) "abc".Guanaco
Yes it solves the problem. I know I can fix it this way (or the other ways I show in the lines preceding it). That's not the problem. The problem is that it behaves differently in Java 8 than Java 7.Khiva
I am only surprised that this didn't result in a compile-time "ambiguous type" error. I believe it should be an error because there is no sane way to reason about what the compiler would be supposed to do.Guibert
@MarkoTopolnik But Test.<Object>getVal() and Test.getVal() should be equivalent right? So I am confused why String.valueOf(Test.<Object>getVal()) works but String.valueOf(Test.getVal()) does not.Khiva
The second case is subject to target type inference, which did not exist in Java 7. That might be a clue, and char[] is "the most specific type" which is legitimate at that position.Guibert
This is not a good answer. It says what happens in each case, but for the actual question of why the difference exists, it simply asserts that such information is useless. "Don't do that" won't help people who are stuck with someone else's design decisions or people who want to understand how Java has changed with the new version.Disfigurement
@Jirka-x1: I am with Paul on this one. Why would you return an instance of String with an explicit cast instead of an instance of T? If you really want to do that then you are misusing generics in the first place, IMO. So really can't call it a bug in the language, I think. Can you reproduce this behaviour by return an instance of T?Unsightly
@BheshGurung What do you mean? T is a type variable, you mean passing it as parameter? I do exactly that in the few lines above. But Java still allows using generic methods without type parameters.Khiva
@Jirka-x1: I mean you shouldn't really have to use @SuppressWarnings("unchecked") on your generic method.Unsightly
@PaulBellora What do you mean by bug? It passes through the compiler. Moreover, there are usecases where this can be useful. (Say getVal would return a value from the database then Test.<String>getVal(fld) and Test.<Integer>getVal(fld) could be used to return String and Integer respectively)Khiva
@BheshGurung this is similar to java.util.Collections.emptyList() Moreover, imagine this is simply a method provided by a library and you have no power over it. Still it is a warning, not an error.Khiva
@Jirka-x1: I mean it's kind of like class A {} class B extends A {} B b = (B) new A();. That compiles fine too.Unsightly
@Disfigurement I have updated the answer to be a little more specific about the general cause of the difference. Is this what you would expect from a solid answer, or would only a detailed breakdown using quotations from the JLS rise to your standard?Guibert
Would the invocation be considered raw in Java 7?Mauser
@SotiriosDelimanolis I think in Java 7 type inference would only work in an assignment, where the target type is obvious.Guibert
Why is char[] the most specific overload?Reeta
@Reeta Be more clear. Do you know which are the candidate overloads?Guibert
char[] is more specific because it's a subclass of Object. But you haven't really explained that, nor why it gets to pick. You've just said "it gets picked in Java 8" which everybody already knows if they read the question.Reeta
@Reeta I have explained that it gets picked because it is the most specific type. The question does not say that. What further explanation do you think is needed?Guibert
Could you please elaborate on what broken idiom you refer to? You mean that getVal() should not be called this way (I should use Test.<String>getVal() instead)? Or do you mean the way getVal is declared (I do not think this is a problem, it is just a normal generic method).Khiva
This is why I don't think there is any compelling case to complain about the changed compiler output between Java 7 and 8. The compiler merely showed you that the unchecked cast warning has been there for a good reason all along.Guibert

© 2022 - 2024 — McMap. All rights reserved.