21

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

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

(接近最小,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

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

            true ? "sss" :

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

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

这给出了警告:

4: redundant cast to java.lang.Object

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

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

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

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

4

8 回答 8

14

从 Java 8 开始,关于“模糊方法调用”的错误是正确的。

即使在 Java 8 之前,您也可以编写

char c = fail();
Object o = fail();

没有编译器错误。当您将条件 likecondition? 'a': genericMethod()传递给方法 likeString.valueOf(…)时,编译器会推断<Object>fail()选择String.valueOf(Object)由于其有限的类型推断。

但是 Java 8 引入了Poly Expressions

独立表达式的类型可以完全由表达式的内容来确定;相反,poly 表达式的类型可能会受到表达式的目标类型(§5(转换和上下文))的影响。

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

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

由于char不是 的子类型Object,也不Object是 的子类型,因此char方法和 ,String.valueOf(Object)都不String.valueOf(char)更具体的 ,因此会生成编译器错误。


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


该问题的解决方法取决于上下文,并且没有足够的信息。请注意,只需要一个不可分配给的分支char,以使该方法String.valueOf(char)不适用。只要您插入评估为String. 您还可以使用它SurroundingClass.<Object>fail()来获得与 Java 8 之前的编译器推断出的相同类型。

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

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 {
    }
}

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

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

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

当然,还有其他可能的解决方法,如果您只是接受引入更多代码,例如用于字符串转换的专用辅助方法而无需重载或用于 throwing 方法的非泛型包装器方法。或用于泛型调用的临时变量或类型转换或显式类型等。此外,当使用"" + (expression)or(expression).toString()代替 时String.valueOf(expression),表达式不是 poly 表达式,因此不会产生“模糊方法调用”错误。

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

于 2020-11-17T13:53:53.083 回答
4

问题是三元运算符的两个分支返回不同的类型。

这个怎么样:

    System.out.println(
        String.valueOf(
            true ? (Object)'a' : fail()
        )
    );
于 2020-11-14T22:18:17.860 回答
3

明确地装箱是一种可能性:

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;
    }
}
于 2020-11-14T22:18:58.417 回答
2

简单问题的简单回答:

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

只要您没有任何null值,此代码就可以工作。要涵盖null值,您需要引入一个额外的方法:

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

两种解决方案都不需要编辑多行? :

编译器警告和错误都不是错误。在错误情况下,您为编译器提供的信息太少,无法选择正确的方法,在第二次尝试中,您告诉编译器将一个 Object 强制转换为一个不必要且值得至少产生警告的 Object ;-)

于 2020-11-21T08:45:46.833 回答
2

您可以将电话与供应商联系起来,以获得以下方法:

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; }
 }
于 2020-11-23T15:12:19.660 回答
1

将表达式的结果提取到局部变量:

T obj =
        true ? 'a' :
                true ? 'b' :
                        true ? 'c' : fail();
System.out.println(String.valueOf(obj));
于 2020-11-21T09:29:51.700 回答
0

'a'不是String符号;将其替换为"a"(而且我也不完全理解用例 - 或者可能会使用正则表达式引擎完全不同地编写它)。但是,为了避免毫无意义的投射......只需检查一下instanceof,你甚至试图投射什么。同样,模棱两可的调用和无用的来回转换几乎没有空间。

于 2020-11-23T15:25:16.897 回答
-1

如果我忽略我的前瞻性要求,String我可以写:

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

要同时处理这两种情况charString我可以使用奇怪的:

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

或者使用 Java 序列化来做一些有用的事情:

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

它应该被认为是一个错误,但我不能打扰与 bugs.java.com 的斗争。

另一种解决方法是引入不必要的本地:

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

这不是必需的,但可以将类型参数设为fail()显式并避免任何讨厌的显式转换:

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

可爱的语法!或者,fail()可以有一个多余的演员表......

于 2020-11-14T22:06:29.420 回答