"redundant cast to java.lang.Object" warning for necessary cast
Explicitly boxing the character is one possibility:
class Code {
public static void main(String[] args) throws Throwable {
System.out.println(
String.valueOf(
true ? Character.valueOf('a') : fail()
)
);
}
private static <R> R fail() {
throw null;
}
}
The error about an “ambiguous method invocation” is correct since Java 8.
Even before Java 8, you could write
char c = fail();
Object o = fail();
without compiler errors. When you pass a conditional like condition? 'a': genericMethod()
to a method like String.valueOf(…)
, the compiler inferred <Object>
for fail()
and picked String.valueOf(Object)
due to its limited type inference.
But Java 8 introduced Poly Expressions:
The type of a standalone expression can be determined entirely from the contents of the expression; in contrast, the type of a poly expression may be influenced by the expression's target type (§5 (Conversions and Contexts)).
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>
for fail()
. 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)
and String.valueOf(char)
are applicable, as it doesn’t matter whether we unbox the Character
after invoking fail()
or box the char
of the literal 'a'
.
Since char
is not a subtype of Object
and Object
is not a subtype of char
, neither method, String.valueOf(Object)
nor String.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 method String.valueOf(char)
inapplicable. That will happen, as soon as you insert the branch that evaluates to String
. You could also use SurroundingClass.<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.
class Code {
public static void main(String[] args) throws SpecificExceptionType {
System.out.println(
String.valueOf(switch(0) {
case 0 -> 'a';
case 1 -> 'b';
case 2 -> 'c';
default -> throw fail();
})
);
}
private static SpecificExceptionType fail() {
return new SpecificExceptionType();
}
static class SpecificExceptionType extends Exception {
}
}
If switch expressions are not feasible, you could use
System.out.println(
String.valueOf(
true ? 'a' :
true ? 'b' :
true ? 'c' :
Optional.empty().orElseThrow(Code::fail)
)
);
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 Throwable
declarations. 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 of String.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).
Simple answer to a simple question:
class Code {
public static void main(String[] args) throws Throwable {
System.out.println((
true ? 'a' :
true ? 'b' :
true ? 'c' :
fail()).toString()
);
}
private static <R> R fail() {
throw null;
}
}
This code works as long as you do not have any null
values. To cover also null
values you would need to inroduce an additional method:
class Code {
public static void main(String[] args) throws Throwable {
System.out.println(valueOf(
true ? 'a' :
true ? 'b' :
true ? 'c' :
fail()
)
);
}
private static <R> R fail() {
throw null;
}
static String valueOf(Object object) {
return String.valueOf(object);
}
}
Both solutions do not require to edit the many lines of ? :
Neither the compiler warning nor the error is a bug. In the error case you give the compiler too less information for the selection of the right method, in the second attempt you tell the compiler to cast an Object to an Object which is unnecessary and worth to produce at least a warning ;-)
The problem is that the two branches of the ternary operator return different types.
How about this:
System.out.println(
String.valueOf(
true ? (Object)'a' : fail()
)
);