"redundant cast to java.lang.Object" warning for necessary cast
Asked Answered
P

8

21

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?)

Poodle answered 14/11, 2020 at 22:6 Comment(6)
Isn't true ? 'a' : fail() equivalent to just 'a'? What is the purpose of this construct?Dipetalous
@JimGarrison true is just to make the example code simple. The real code has lines like type == SymbolToken.assign ? '=' :.Poodle
I think you need to provide an example that more closely approximates your use case. Note that changing the 'a' to "a" removes the warning, but I assume you know that already. Please share more of your mental model of what you're trying to achieve.Dipetalous
@JimGarrison Then it gets obscure. It's a preprocessor for itself. "a"` is converted to 'a' because "abc" is indistinguishable from 'abc and so the preprocessor (which is just a stop gap) guesses that a single letter is a char constant. In any case, in an equivalent situation the char could be return by some random function.Poodle
Well, without a more concrete example of your true objective, I think the best you're going to get is the embedded cast from char to Object. Could this be an XY Problem?Dipetalous
@JimGarrison No. No it isn't. Would it help if I replaced the main with a method parameterised with the values and added several ? :s.Poodle
T
15

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 pro­du­cing 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).

Terrence answered 17/11, 2020 at 13:53 Comment(4)
I don't claim to be able to follow the JLS there. It's odd that adding a dummy Optional.empty() works for orElseThrow. I hope you get the 'lifejacket' badge.Poodle
For a chained method invocations like .empty().orElseThrow(…), the type inference still is limited for the first part(s), so it infers Object here. This still is a kludge, the switch expression is the way to go, as it allows to integrate the throwing branch into the expression without a pseudo return type. Since pattern matching is a planned feature, this solution will be applicable to other conditions than just a number in the future.Terrence
@Terrence I don't understand why a cast to Object is needed here. Shouldn't lub(char, Object) = Object automatically? Even casting it to R works but why? public static <R> void main(String[] args) { String.valueOf(true ? 'a' : true ? 'b': true ? 'c' : (R)fail()) }Null
@ThirumalaiParthasarathi why do you assume the second type to be Object?Terrence
D
4

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()
        )
    );
Dipetalous answered 14/11, 2020 at 22:18 Comment(9)
That should work, but that's a lot of code if there are many lines of ? :.Poodle
@TomHawtin-tackline compared to what? All of your alternatives contain at least as much code as that.Terrence
@Terrence As mentioned in the question, I want to be able to add other ? :s into the chain (and have in the real code).Poodle
@TomHawtin-tackline like a? (Object)b: c? d: e? f: g? Where’s the problem?Terrence
@Terrence To do it only for the first one would be weird, wouldn't it? Particularly when it's a question of selecting the correct overload.Poodle
@TomHawtin-tackline you are changing the topic. But the biggest problem with your question is that you keep on insisting that your real life case is different, but don’t show your real life case. E.g. I assume, the real life case is not invoking String.valueOf, so which method are you actually invoking and does it need to be that heavily overloaded? Why does it matter which overload is invoked? Second, your real life case is not 'a', so what is actually? A parameter? Can’t you declare it as Object in the first place? Truly a constant? How about declaring a named constant, using Object?Terrence
@Terrence The real code, which doesn't really matter, does use String.valueOf, but it doesn't use System.out.println nor is it directly in main. The symbols were literal characters in =()[]+**/.!&|:;@?^% (* is the value for two conditions).Poodle
@TomHawtin-tackline then, I don’t get your requirements. Why does it matter whether inserting condition? "...": would turn the code from using String.value(char) to String.value(Object)? The behavior would be the same. Or just replace String.valueOf with "" + and you’ve solved the problem with even shorter code. Or, since there’s no null involved, use (…).toString() instead of String.valueOf(…).Terrence
@Terrence String.valueOf is necessary in the case of all literals are characters but the fail() remains. String.valueOf is consistent with the rest of the codebase. Also "" + always looks a hack and .toString (and anything at the fail() end) is a bit far from the action.Poodle
A
3

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;
    }
}
Amieeamiel answered 14/11, 2020 at 22:18 Comment(1)
That should work, but that's a lot of code if there are many lines of ? :.Poodle
S
2

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 ;-)

Snuck answered 21/11, 2020 at 8:45 Comment(0)
P
2

You can wrap the call with a Supplier to get a way with:

class Code {
   public static void main(String[] args) throws Throwable {
        System.out.println(((Supplier<Object>) () ->
                true ? 'a' :
                        false ? 'b' :
                                false ? 'c' :
                                        fail()).get());
    }
    private static <R> R fail() { throw null; }
 }
Pleasure answered 23/11, 2020 at 15:12 Comment(2)
Cunning. Made me wonder whether there is an identity static method. new javax.swing.tree.DefaultMutableTreeNode(Object) calls toString on its argument for toString. Better Object.class.cast(Object) works and is more or less equivalent to the original code.Poodle
+500 bounty rep. This answer inspired Object.class.cast. Inspired is better than just handed on a plate, because it makes me feel good about myself.:)Poodle
Z
1

Extract the result of the expression to a local variable:

T obj =
        true ? 'a' :
                true ? 'b' :
                        true ? 'c' : fail();
System.out.println(String.valueOf(obj));
Zobias answered 21/11, 2020 at 9:29 Comment(0)
H
0

'a' is not a String notation; replace that with "a" (and I also don't fully understand the use case - or maybe would write it whole different using the regex engine). However, in order to avoid pointless casting ...simply check with instanceof, what you are even trying to cast. Alike this, there is little space for ambiguous calls and useless casting back and forth.

Hild answered 23/11, 2020 at 15:25 Comment(0)
P
-1

If I ignore my forward looking requirement for String I could write:

        String.valueOf((Character)(
            true ? 'a' :
            fail()
        ))

To handle both char and String I can use the weird:

        String.valueOf((Comparable<?>)(
            true ? 'a' :
            fail()
        ))

Or use Java Serialization for something useful:

        String.valueOf((java.io.Serializable)(
            true ? 'a' :
            fail()
        ))

It should presumably considered a bug, but I can't be bothered fighting bugs.java.com.

An alternative fix is to introduce a unnecessary local:

    Object symbol = 
            true ? 'a' :
            fail();
    System.out.println(String.valueOf(symbol)); 

It shouldn't be necessary, but it is possible to make the type parameter to fail() explicit and avoid any nasty explicit casts:

        String.valueOf(
            true ? 'a' :
            Code.<Object>fail()
        )

Lovely syntax! Alternatively, fail() could have a redundant cast...

Poodle answered 14/11, 2020 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.