Hegdekar
Hegdekar

Reputation: 1167

Ambiguous method call

The below code block

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 + "]");
    }
}

gives an error for the method call in the catch block stating

error: reference to log is ambiguous log("main", "Error in main",e); ^ both method log(String,Object,Throwable) in Test and method log(String,String,Object) in Test match

I'm not able to understand it. I went through lot of questions here but most of them either have varargs or generics involved. Couldn't determine what's wrong with the above code though. Going through the JSL unable to determine what rule am I violating. Any help in understanding this would be great.

I know some questions talked about

as solutions to make it clear for the compiler which method to pickup. But I'm looking forward to understand why the overloaded methods don't work here. Also if type-casted the second parameter to Object the code works fine, But why?

Some question did refer to type inference improved in Java 8 than 7, so to clarify I'm running this on Java 8.

Upvotes: 1

Views: 6745

Answers (6)

Holger
Holger

Reputation: 298233

When you call log with the argument types (String,String,Throwable), both methods

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

are applicable, but neither is more specific than the other. For the second argument, String is a more specific type than Object, but for the third, Throwable is more specific than Object.

You can solve this by adding another overload

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

as then, this new overload is more specific than the others and will be selected when invoking log with (String,String,Throwable).

An alternative is to make the second method more versatile by changing it to

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)) + "]");
}

Now it accepts multiple message arguments, but will behave the same as before when being called with only one argument. Since varargs methods have less precedence when multiple methods are applicable, calling log with (String,String,Throwable) will select the first method which has (String,Object,Throwable) parameter types.

Upvotes: 1

Jean-Baptiste Yunès
Jean-Baptiste Yunès

Reputation: 36401

If you call log with a String, a String and a Throwable, how would you solve the ambiguity? There is no preference regarding the order of arguments... So Java is both able to "promote" the String to Object and call the first one, or "promote" the Throwable to an Object and chose the second one, with no preference, thus the ambiguity.

If you enforce the type of the second argument to Object then of course the compiler will have no choice, it looks to a method whose signature begin with (String,Object, thus the first one.

Your problem is that you are overloading the function by contravariance, one parameter by generalizing the type, and another parameter by specializing the type. There is no solution. Prefer not doing it...

Upvotes: 1

name not found
name not found

Reputation: 622

You have two log methods and both of them would match the call log("main", "Error in main",e);

Because you pass the following arguments:

  • "main" -> String
  • "Error in main" -> String
  • e -> Exception

now which of your log methods should be called?

Both methods would accept these arguments.

log method 1

static void log(String methodName, Object message, Throwable t)
  • "main" -> String -> matches String methodName
  • "Error in main" -> String is also an Object -> matches Object message
  • e -> Exception -> Exception is a Throwable -> matches Throwable t

log method 2

static void log(String requestId, String method, Object message)
  • "main" -> String -> matches String requestId
  • "Error in main" -> String -> matches String method
  • e -> Exception -> Exception is an Object -> matches Object message

Upvotes: 2

DamKoVosh
DamKoVosh

Reputation: 144

The Error Msg states, that there are two Methods that could solve your request log(String,Object,Throwable) and log(String,String,Object). The Interpreter could either cast your String to Object and keep the Exception a Throwable or it could cast the Throwable to an Object and keep the String as it is. Both possibilities require one cast, so it throws the exception instead of guessing which variant you would prefer.

Upvotes: 1

Tim Biegeleisen
Tim Biegeleisen

Reputation: 521399

The ambiguity is arising because the third parameter Exception is both a Throwable and an Object. Use this version for the second logging method:

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

Now the following two calls are not ambiguous:

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

Upvotes: 1

khelwood
khelwood

Reputation: 59112

Doesn't appear to be anything to do with generics.

You have a method call

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

and the compiler doesn't know if you're trying to call

log(String, Object, Throwable) 

or

log(String, String, Object)

since both would match and neither is more specific than the other.

To make it clear, you could cast:

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

if you want the first, or

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

if you want the second.

Or write a log method more precisely matching the arguments you intend to pass to it.

Upvotes: 6

Related Questions