必要转换的“冗余转换为 java.lang.Object”警告

Tom*_*ine 21 java casting compiler-warnings ambiguous-call compiler-bug

考虑这个最小的、可重现的例子

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

(接近最小,true是一个有用的布尔表达式的替代品。除了第一个之外,我们可以忽略? :(在实际代码中,有很多)。)

这“显然”给出了错误。

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
Run Code Online (Sandbox Code Playgroud)

好的,让我们修复它。这是String.valueOf(Object)我想要的超载 - 我以后可能想添加:

            true ? "sss" :
Run Code Online (Sandbox Code Playgroud)

(事实上​​,我之前确实有类似的东西,但现在已经删除了该功能。)

        String.valueOf((Object)(
            true ? 'a' :
            fail()
        ))
Run Code Online (Sandbox Code Playgroud)

这给出了警告:

4: redundant cast to java.lang.Object
Run Code Online (Sandbox Code Playgroud)

这是编译器警告或错误中的错误,我该如何修复它以使代码合理且没有警告或错误?

(编辑:我稍微改变了 MRE。throws Throwable来自模板。真正的代码确实使用文字 chars* 而String.valueOf在其他地方它使用String.valueOf(char)重载,所以toString()是有问题的(哦 Java!)。代码避免了全局状态,例如System.out, 和symbol并且fail在不同的类中。“开关”是不可枚举的类型。fail是类断言方法的伴随,所以这就是它在内部抛出(未经检查的非空)异常的原因。

我实际上是如何修复它的,与此无关,我重新排列了代码,因此那里也有一些文字字符串。否则,我会使用无意义Object.class.cast(Object). 我真正想知道的是:wtf?

*实际上,真正的真实代码通过不同语言的词法分析器,它不区分文字字符、字符串、各种数字、布尔值、枚举等。为什么会这样?)

Hol*_*ger 14

自 Java 8 以来,关于“模棱两可的方法调用”的错误是正确的。

甚至在 Java 8 之前,你就可以编写

char c = fail();
Object o = fail();
Run Code Online (Sandbox Code Playgroud)

没有编译器错误。当你通过像有条件condition? 'a': genericMethod()像的方法String.valueOf(…),推断编译器<Object>fail(),拿起String.valueOf(Object)由于其有限的类型推断。

但是 Java 8 引入了Poly 表达式

独立表达式的类型完全可以根据表达式的内容来确定;相比之下,poly 表达式的类型可能会受到表达式目标类型的影响(§5 (Conversions and Contexts))。

泛型方法的调用和包含poly 表达式的条件(即泛型方法的调用)都是poly 表达式。

所以,试图调用String.valueOf(char)是把条件有效,因为我们可以推断出<Character>fail()。请注意,这两种方法都不适用于严格的调用上下文,因为这两种变体都需要装箱或拆箱操作。在松散的调用上下文中String.valueOf(Object)String.valueOf(char)都适用,因为我们是拆箱Character后调用fail()还是装箱char文字的都没有关系'a'

由于char不是的子类型ObjectObject不是的一个子类型char,既不方法,String.valueOf(Object)也不String.valueOf(char)更具体的,因此,会产生一个编译错误。


对警告的判断比较困难,因为没有正式的警告标准。在我看来,每一个声称源代码工件已经过时的编译器警告都是不正确的,尽管代码在删除它后不会做同样的事情(或者删除它甚至会引入错误)。有趣的是,警告在 Java 7 的 版本中已经存在javac,删除强制转换确实没有区别,所以也许,它是需要更新的剩余部分。


该问题的解决方法取决于上下文,关于它的信息不足。请注意,只需要一个不可分配给 的分支char,以使该方法String.valueOf(char)不适用。一旦您插入计算结果为 的分支,就会发生这种情况String。您还可以使用SurroundingClass.<Object>fail()来获取 Java 8 之前的编译器推断的相同类型。

或者完全删除通用签名,因为这里不需要它。泛型方法fail()似乎是在表达式上下文中使用 throwing 方法的一种变通方法。更清洁的解决方案是表达式的工厂方法,例如

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 {
    }
}
Run Code Online (Sandbox Code Playgroud)

如果 switch 表达式不可行,您可以使用

System.out.println(
    String.valueOf(
        true ? 'a' :
        true ? 'b' :
        true ? 'c' :
        Optional.empty().orElseThrow(Code::fail)
    )
);
Run Code Online (Sandbox Code Playgroud)

两者都具有特定于潜在抛出异常的实际类型的优点,并且不需要诉诸未经检查的异常或throws Throwable声明。第二个可能会让人感觉很笨拙,但不过是定义一个从不返回任何内容的通用方法。

当然,还有其他的可能来解决它​​,如果你只是接受引入更多的代码,比如没有重载的字符串转换的专用帮助方法或抛出方法的非通用包装方法。或者临时变量或类型转换或泛型调用的显式类型等。此外,当使用"" + (expression)(expression).toString()代替 时String.valueOf(expression),表达式不是 poly 表达式,因此不会产生“模棱两可的方法调用”错误。

当然,由于这是一个错误警告,您也可以保留强制@SuppressWarnings("cast")转换并向方法添加 a (并等待编译器开发人员修复此问题)。

  • 对于像“.empty().orElseThrow(…)”这样的链式方法调用,类型推断仍然限于第一部分,因此它在这里推断“Object”。这仍然是一个拼凑, switch 表达式是可行的方法,因为它允许将抛出分支集成到表达式中,而无需伪返回类型。由于模式匹配是一项计划中的功能,因此该解决方案将来不仅适用于数字,还适用于其他条件。 (2认同)