11

I have the following available methods in a Utils class:

protected <U> U withTx(Function<OrientGraph, U> fc) {
    // do something with the function
}

protected void withTx(Consumer<OrientGraph> consumer) {
    withTx(g -> {
        consumer.accept(g);
        return null;
    });
}

And in a method of myClass I have:

withTx(g -> anotherMethod(g));

The second piece of code has a compilation error:

The method withTx(Function<OrientGraph, Object>) is ambiguous for the type myClass

I guess this comes from the compiler, which is not able to determine if the lambda is a Consumer or a Function. Is there a noble way to disambiguate this situation?

Whatever the method anotherMethod returns (void, Object, anything), I don't want to use this return value.

One solution is to do:

withTx(g -> { anotherMethod(g); });

But I wanted to know if there was something better, because this triggers SonarLint.

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
Matthias Beaupère
  • 1,731
  • 2
  • 17
  • 44

2 Answers2

9

From the "Effective Java" by Joshua Bloch:

Do not provide a method with multiple overloadings that take different functional interfaces in the same argument position if it could create a possible ambiguity in the client.

The easiest way to avoid this problem is not to write overloadings that take different functional interfaces in the same argument position.

One more possible solution could be to use a different names for these two methods:

<U> U withTxFunction(Function<OrientGraph, U> fc);

void withTxConsumer(Consumer<OrientGraph> consumer);

A good example of this approach one can find in the Java API itself, for example in the  IntStream interface:

mapToDouble(IntToDoubleFunction mapper);

mapToLong(IntToLongFunction mapper);

Update:

I'd like to add a clarification of why does the compiler complain? That is because...

The lambda g -> anotherMethod(g) can be assigned to both Function<T, R> and Consumer<T>:

Function<T, R> func  = g -> anotherMethod(g);

Consumer<T> consumer = g -> anotherMethod(g); // here you just ignore the return value

<T> T anotherMethod(T t) { ... }

So, when you write withTx(g -> anotherMethod(g)), you get the "Ambiguous method call" error, the compiler fails to find out which of the overloaded method should be used:

withTx(function) OR withTx(consumer) ... ?
Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
2

Actually, you already have the resolution to your problem, but in a different place, because you write:

withTx(g -> {
    consumer.accept(g);
    return null; // this calls the `Function` variant of `withTx`
});

But anyways, you have this problem because the return type of your function anotherMethod() is void, and the compiler doesn't know what to choose from, the Function<OrientGraph, Void> or Consumer<OrientGraph>. Consumer itself is supposed to represent a function that accepts something and returns nothing.

withTx(g -> { anotherMethod(g); }); calls the Consumer variant explicitly.

If you want to call the Function variant, do: withTx(g -> { anotherMethod(g); return null; });

Coder-Man
  • 2,391
  • 3
  • 11
  • 19