【问题标题】: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 类型,所以对我来说,你的解决方案只是令人困惑。如果有的话,您应该有类似 customObjectextraInfo 或类似 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 并保持ExceptionThrowable,或者它可以将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 -> ExceptionThrowable -> 匹配 Throwable t

      记录方法二

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

      【讨论】:

        【解决方案5】:

        如果您使用StringStringThrowable 调用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 更具体的类型,但对于第三个参数,ThrowableObject 更具体。

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

          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
            相关资源
            最近更新 更多