How to detect ambiguous method calls that would cause a ClassCastException in Java 8?
Asked Answered
A

2

13

We are currently in the process of migrating an application from Java 7 to Java 8. After fixing a some compilation issues, I stumbled upon an issue similar to the following question: ClassCast Error: Java 7 vs Java 8.

To summarise, here is a sample code that shows the issue:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception 
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(String param) {
        // do some computation based on param...
        return (T) result; // actual return type only depends on param so the caller knows what to expect
    }
}

The idea was that we would trust that the caller knows the expected type, and this would avoid him an explicit cast (I’m not saying this was a good idea…). In a lot of cases, the caller just expects an Object so there was no implicit cast at all.

As stated in the question above, the String.valueOf example worked fine in Java 7 because there was no type inference, hence Object was assumed. Now in Java 8, the compiler chooses the most specific type (here char[]), which causes a ClastCastException at runtime.

The problem is that we have around 350 calls of this getVal method. Is there a way to detect overloaded method calls that would differ between Java 7 and Java 8? I.E. detect when the Java 8 compiler would select a different method from the Java 7 compiler.

Aylsworth answered 28/5, 2015 at 21:58 Comment(6)
We had a similar method which was also called in hundreds of places. I replaced it introducing additional Class<T> clazz parameter. This way you can have an explicit error handling: either return null or throw some more specific exception. I just did this manually spending like half an hour. I advise you to do the same. I don't know how to automatize it, thus this is not qualified as an answer to your question.Localism
There is a simple answer: don’t suppress “unchecked” warnings. They already tell you that your code is wrong. If you want to fix your code now that it is too late, just search for occurrences of @SuppressWarnings("unchecked")Royroyal
@Royroyal Could you please tell that to my colleague when he implemented this in 2011? ;-) I already knew it was a bad idea in the first place (although not as bad as it is now in Java 8). Also, there is no @SuppressWarnings on the method call themselves, it's only in the declaration of getVal. Removing this annotation here will thus not show any of the problematic usages.Aylsworth
Well, the way getVal has been declared is the problem. So looking for the annotation will lead you to the right spot. Then, fixing the signature will, of course, raise compiler errors at the callers so they’ll be easy to find. Unless you use an IDE which refactors the signature and the callers in one go, which is a viable alternative. Of course, once the annotation has guided you to the problematic method, finding its callers works even without changing it, if you use a decent IDE. So the necessary tools are available…Royroyal
I think this will be the way to go indeed. This does not really answer the question since I'll have to change much more code than the few problematic calls but at least it will solve our issue. Also, it seems that changing the return type to Object will automatically fix the problematic calls, so ironically there will be no change there (and I will maybe never know where they were :-).Aylsworth
I must add that even if I fixed our getVal method, I still fear that there are some other methods somewhere which may be declared similarly (maybe not in such an obvious way) which could cause the same issue, and be difficult to spot…Aylsworth
M
2

A better alternative would be:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(T param) {
        // do some computation based on param...
        return   param; // actual return type only depends on param so the caller knows what to expect
    }
}

which will work in both Java 7 and Java 8.

Mouldy answered 29/5, 2015 at 10:56 Comment(1)
This is not an alternative since you impose that the return type is the same as the parameter (which in my case is a String and I cannot easily change that). Also, the question is to identify the cases where overloaded method calls are different between Java 7 and 8, to avoid changing all of them or searching them by hand.Aylsworth
A
1

In the end the solution was to change getVal() to return Object:

    public static Object getVal(String param) {
        // do some computation based on param...
        return result;
    }

and add a second method that also takes the desired class as parameter:

    public static <T> T getVal(String param, Class<T> clazz) {
        return clazz.cast(getVal(param));
    }

then fix all compilation issues (where caller didn't expect Object) by adding the appropriate class parameter.

Adding a cast would have also worked but it would have caused a lot of warnings for unconditional casts. The unconditional cast is actually still there (through the clazz parameter), but this allows to easily identify all callers that need a cast as they use the method with 2 parameters.

Additionally – and this is very specific to this case – it appeared that the param itself was often the result of a method call on some TypedParam<T> where T was the expected return type of getVal(), and which also contained T's class.

I could thus implement an additional convenience method:

    public static <T> T getVal(TypedParam<T> param) {
        return getVal(param.stringValue(), param.getValueClass());
    }

and replaced all getVal(param.stringValue()) by just getVal(param).

This solution does not resolve the general casedetect overloaded method calls that would differ between Java 7 and Java 8 – but it resolves it for methods that are known to cause this problem. And we didn't find it elsewhere since then.

Aylsworth answered 11/1, 2016 at 0:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.