【问题标题】:Ambiguous method call模棱两可的方法调用
【发布时间】:2020-04-01 12:20:06
【问题描述】:

下面的代码块

package com.example;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Test {

    private static final Logger LOGGER = LogManager.getLogger(Test.class);

    public static void main(String[] args) {
        try {
            log("1234", "main", "try");
        } catch (Exception e) {
            log("main", "Error in main",e);
        }

    }

    static void log(String methodName, Object message, Throwable t) {
        LOGGER.error("[Method Name :" + methodName + "] [Message :" + message + "]", t);
    }

    static void log(String requestId, String method, Object message) {
        LOGGER.error("[RequestId :" + requestId + "]" + "[Method Name :" + method + "] [Message :" + message + "]");
    }
}

在catch 块声明中给出方法调用错误

错误:对日志的引用不明确 log("main", "main 中的错误",e); ^ Test 中的方法 log(String,Object,Throwable) 和 Test 中的方法 log(String,String,Object) 都匹配

我无法理解。我在这里遇到了很多问题,但其中大多数都涉及可变参数或泛型。虽然无法确定上述代码有什么问题。 通过 JSL 无法确定我违反了什么规则。任何有助于理解这一点都会很棒。

我知道一些讨论过的问题

  • 重命名方法

  • 参数的显式类型转换

作为解决方案,让编译器清楚地知道要拾取哪种方法。但我很期待了解为什么重载的方法在这里不起作用。此外,如果将第二个参数类型转换为Object,代码可以正常工作,但为什么呢?

有些问题确实提到了在 Java 8 中比 7 改进的类型推断,所以为了澄清我在 Java 8 上运行它。

【问题讨论】:

  • 由于 String 和 Throwable 从 Object 继承,编译器无法确定使用哪个方法,因为两者都匹配。为什么要让message 成为一个对象?我认为您应该删除 Object 作为参数类型的任何使用并改用具体类型,这将使​​如何使用这些方法更加清晰和容易理解,编译器会很高兴
  • 不是问题的答案,但 slf4j 不会遇到这样的问题。
  • @JoakimDanielson 我已经添加了代码作为演示来传达这个想法。我正在围绕记录器编写一个包装器,以便可以隐藏实际实现,并且还可以记录我必须添加的更多参数。通常,对于日志记录位,我更愿意将其声明为 Object,因为它可以灵活地记录任何内容或不必每次都显式调用 toString
  • 几乎每个看到 log 方法的人都希望使用名为 message 的参数的参数是 String 类型,所以对我来说,你的解决方案只是令人困惑。如果有的话,您应该有类似 customObject、extraInfo 或类似 Object 类型的东西,可以补充 message 参数。
  • 是的,我明白了,但从当前的实现开始,我计划使用 Log4J,它允许 Object 作为参数,因此试图保持与此相似。我知道 Log4J 参数,但后来开始考虑围绕 log(Object message) 的包装器。

标签: java java-8


【解决方案1】:

似乎与泛型无关。

你有一个方法调用

log("main", "Error in main",e);

编译器不知道你是否尝试调用

log(String, Object, Throwable) 

或

log(String, String, Object)

因为两者都会匹配,并且没有一个比另一个更具体。

为了清楚起见,您可以投射:

log("main", (Object) "Error in main", e);

如果你想要第一个,或者

log("main", "Error in main", (Object) e);

如果你想要第二个。

或者编写一个log 方法更精确地匹配您打算传递给它的参数。

【讨论】:

    【解决方案2】:

    之所以会产生歧义,是因为第三个参数Exception 既是Throwable 又是Object。将此版本用于第二种记录方法:

    static void log(String requestId, String method, String message) {
        LOGGER.error("[RequestId :" + requestId + "]" + "[Method Name :" + method + "] [Message :" + message + "]");
    }
    

    现在以下两个调用没有歧义:

    log("1234", "main", "try");
    log("main", "Error in main", e);
    

    【讨论】:

    • 这需要每次将日志消息显式转换为字符串,这是我试图避免的。目前我计划使用 Log4J 的实现,它允许 Object 作为参数,因此试图保持与此相似。
    • @Hegdekar 其他答案似乎也需要显式转换。
    • 是的。我试图了解这背后的工作。大致解释器试图找到精确匹配的方法,如果没有,它会尝试提升到父类型。但这是否就像对所有参数进行提升然后找到匹配方法一样?还是对每个进行参数类型提升然后找到匹配方法?对我来说,第一个似乎就是这种情况。
    【解决方案3】:

    错误消息指出,有两种方法可以解决您的请求log(String,Object,Throwable) 和log(String,String,Object)。 解释器可以将您的String 转换为Object 并保持Exception 为Throwable,或者它可以将Throwable 转换为Object 并保持String 不变。这两种可能性都需要一次强制转换,因此它会抛出异常而不是猜测您更喜欢哪个变体。

    【讨论】:

      【解决方案4】:

      您有两个日志方法,它们都将匹配调用 log("main", "Error in main",e);

      因为你传递了以下参数:

      • "main" -> 字符串
      • “主要错误”-> 字符串
      • e -> 例外

      现在应该调用哪些日志方法?

      这两种方法都会接受这些参数。

      记录方法一

      static void log(String methodName, Object message, Throwable t)
      
      • "main" -> String -> 匹配 String methodName
      • “主错误” -> String 也是 Object -> 匹配 Object message
      • e -> Exception -> Exception 是 Throwable -> 匹配 Throwable t

      记录方法二

      static void log(String requestId, String method, Object message)
      
      • "main" -> String -> 匹配 String requestId
      • “主错误”-> String -> 匹配 String method
      • e -> Exception -> Exception 是 Object -> 匹配 Object message

      【讨论】:

        【解决方案5】:

        如果您使用String、String 和Throwable 调用log,您将如何解决歧义?关于参数的顺序没有偏好......所以Java既可以将String“提升”为Object并调用第一个,也可以将Throwable“提升”为Object并选择第二个,没有偏好,因此模棱两可。

        如果你强制 Object 的第二个参数的类型,那么编译器当然别无选择,它会寻找一个签名以(String,Object, 开头的方法,因此是第一个。

        您的问题是您通过逆变来重载函数,通过泛化类型来重载一个参数,通过专门化类型来重载另一个参数。没有解决办法。宁愿不这样做...

        【讨论】:

          【解决方案6】:

          当您使用参数类型(String,String,Throwable) 调用log 时,两种方法都

          static void log(String methodName, Object message, Throwable t)
          static void log(String requestId, String method, Object message)
          

          都适用,但没有一个比另一个更具体。对于第二个参数,String 是比 Object 更具体的类型,但对于第三个参数,Throwable 比 Object 更具体。

          你可以通过添加另一个重载来解决这个问题

          static void log(String methodName, String message, Throwable t) {
            log(methodName, (Object)message, t);
          }
          

          因此,这个新的重载比其他重载更具体,并且在使用 (String,String,Throwable) 调用 log 时将被选中。

          另一种方法是将第二种方法更改为更加通用

          static void log(String requestId, String method, Object... message) {
            LOGGER.error("[RequestId :" + requestId + "]" + "[Method Name :" + method
              + "] [Message :" + (message.length == 1? message[0]: Arrays.toString(message)) + "]");
          }
          

          现在它接受多个消息参数,但在仅使用一个参数调用时的行为与以前相同。由于在多个方法适用时可变参数方法的优先级较低,因此使用(String,String,Throwable) 调用log 将选择具有(String,Object,Throwable) 参数类型的第一个方法。

          【讨论】:

            猜你喜欢
            • 2019-12-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-09-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多