Consider this Minimal, Reproducible Example :
interface Code {
static void main(String[] args) {
symbol(
String.valueOf(
true ? 'a' :
true ? 'b' :
true ? 'c' :
fail()
)
);
}
private static void symbol(String symbol) {
System.out.println(symbol);
}
private static <R> R fail() {
throw null;
}
}
(Being near minimal, true is a stand in for a useful boolean expression. We can ignore beyond the first ? : (in the real code, there are lots).)
This 'obviously' gives the error.
4: reference to valueOf is ambiguous
both method valueOf(java.lang.Object) in java.lang.String and method valueOf(char) in java.lang.String match
Okay let's fix it. It's the String.valueOf(Object) overload I want - I might later want to add:
true ? "sss" :
(In fact I did have something similar earlier, but have now removed the feature.)
String.valueOf((Object)(
true ? 'a' :
fail()
))
This gives the warning:
4: redundant cast to java.lang.Object
Is this a bug in the compiler warning or error, and how do I fix it so the code is reasonable and there are no warnings or errors?
(Edits: I've change the MRE slightly. throws Throwable was from a template. The real code does use literal chars* and String.valueOf Elsewhere it uses the String.valueOf(char) overload, so toString() is problematic (oh Java!). The code avoids global state, such as System.out, and symbol and fail are in different classes. The "switch" is of a non-enumerable type. fail is a companion to an assert-like method, so that's why it throws an (unchecked non-null) exception internally.
How I actually fixed it was, unrelatedly, I rearranged code so there were some literal strings in there too. Otherwise, I would have used the pointless Object.class.cast equivalent of (Object). What I really want to know is: wtf?
*Actually the real real code goes through a lexer for a different language that doesn't distinguish between literal char, string, various numbers, boolean, enums, etc. Why would it?)
The error about an “ambiguous method invocation” is correct since Java 8.
Even before Java 8, you could write
without compiler errors. When you pass a conditional like
condition? 'a': genericMethod()to a method likeString.valueOf(…), the compiler inferred<Object>forfail()and pickedString.valueOf(Object)due to its limited type inference.But Java 8 introduced Poly Expressions:
Both, an invocation of a generic method and a conditional containing a poly expression (i.e. the invocation of a generic method), are poly expressions.
So trying to invoke
String.valueOf(char)is valid with that conditional, as we can infer<Character>forfail(). Note that neither method is applicable in a strict invocation context, as both variants require a boxing or unboxing operation. In a loose invocation context, both,String.valueOf(Object)andString.valueOf(char)are applicable, as it doesn’t matter whether we unbox theCharacterafter invokingfail()or box thecharof the literal'a'.Since
charis not a subtype ofObjectandObjectis not a subtype ofchar, neither method,String.valueOf(Object)norString.valueOf(char), is more specific, hence, a compiler error is generated.Judging about the warning is more difficult, as there is no formal standard for warnings. In my opinion, every compiler warning that claims that a source code artifact was obsolete despite the code will not do the same after removing it (or removing it will even introduce errors), is incorrect. Interestingly, the warning does already exist in Java 7’s version of
javac, where removing the cast truly makes no difference, so perhaps, it’s a leftover that needs to be updated.Workarounds for the issue depend on the context and there’s not enough information about it. Mind that there is only one branch needed that is not assignable to
char, to make the methodString.valueOf(char)inapplicable. That will happen, as soon as you insert the branch that evaluates toString. You could also useSurroundingClass.<Object>fail()to get to the same type that pre-Java 8 compilers inferred.Or drop the generic signature entirely, as it is not needed here. The generic method
fail()seems to be a work-around to have a throwing method in an expression context. A cleaner solution would be a factory method for the expression, e.g.If switch expressions are not feasible, you could use
Both have the advantage of being specific about the actual type of potentially thrown exceptions and don’t need resorting to unchecked exceptions or
throws Throwabledeclarations. The second might feel hacky, but not more than defining a generic method that never returns anything.Of course, there are other possibilities to solve it, if you just accept the introduction of more code, like a dedicated helper method for the string conversion without overloads or a non-generic wrapper method for the throwing method. Or a temporary variable or type casts or explicit types for the generic invocations, etc. Further, when using
"" + (expression)or(expression).toString()instead ofString.valueOf(expression), the expression is not a poly expression, hence, not producing an “ambiguous method invocation” error.Of course, since this is a false warning, you could also keep the cast and add a
@SuppressWarnings("cast")to the method (and wait until this gets fixed by the compiler developers).