5

I have a functional interface

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

@FunctionalInterface
public interface SubmitterCompletable extends Submitter {
    @Override
    <T> CompletableFuture<T> submit(Callable<T> task);
}

and two functions

import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

public final class CompletableFutureUtils {
    public static <U> CompletableFuture<U> runAsync(Callable<U> callable) {
        // ...
    }

    public static <U> CompletableFuture<U> runAsync(Callable<U> callable, Executor executor) {
        // ...
    }
}

and I want to create SubmitterCompletables from these functions using lambda expressions or method references. The first one works fine by using method references.

SubmitterCompletable submitterCompletable = CompletableFutureUtils::runAsync;

For the second one, however, I have to use a lambda expression to pass an Executor and it doesn't work.

Executor executor = /* ... */;
SubmitterCompletable submitterCompletable = c -> CompletableFutureUtils.runAsync(c, executor);
// Illegal lambda expression: Method submit of type SubmitterCompletable is generic

My question is whether there is a valid lambda expression for this case, or do I have to create an anonymous class in this case?

stonar96
  • 1,359
  • 2
  • 11
  • 39

2 Answers2

1

The issue there is that "a lambda expression can be used for a functional interface only if the method in the functional interface has NO type parameters". (JLS11, 15.27.3 Type of a Lambda Expression) with one exception - this is not the case for congruent method references.

That is why it works in your first example and doesn't in the second:

SubmitterCompletable submitterCompletable = CompletableFutureUtils::runAsync; (OK)
SubmitterCompletable submitterCompletable = c -> <anything> (NOT OK)

There aren't many options out there I could think of to achieve what you want:

  1. Implement the interface (either in-place using an anonymous class as you've mentioned or as a standalone class).

  2. Use an intermediate helper class inside your CompletableFutureUtils that would keep a ref to the executor and expose a method congruent with your Submitter's functional method which will delegate the call to the underlying runAsync(Callable<U> callable, Executor executor) util's method.

Example code:

public final static class CompletableFutureUtils {
    public static <U> CompletableFuture<U> runAsync(Callable<U> callable) {
        ...
    }

    public static <U> CompletableFuture<U> runAsync(Callable<U> callable, Executor executor) {
        ...
    }
    
    public static ExecutorRunnerProxy using(Executor executor) {
        return new ExecutorRunnerProxy(executor);
    }

    public static final class ExecutorRunnerProxy {
        private final Executor executor;

        private ExecutorRunnerProxy(Executor executor) {
            this.executor = executor;
        }

        public <T> CompletableFuture<T> runAsync(Callable<T> task) {
            return CompletableFutureUtils.runAsync(task, executor);
        }
    }
}

Example usage:

SubmitterCompletable submitterCompletable = CompletableFutureUtils::runAsync; 
SubmitterCompletable submitterWithExecutor = CompletableFutureUtils.using(executor)::runAsync;
dshelya
  • 448
  • 3
  • 13
0

There is a valid lambda expression, however, you need to move the generic type in SubmitterCompletable from the method to the class level as long as lambda expression cannot be constructed if the generic type is defined at the method:

@FunctionalInterface
public interface SubmitterCompletable<T> extends Submitter {  // notice the <T>

    @Override
    CompletableFuture<T> submit(Callable<T> task);            // reuses the generic type
}
Executor executor = /* ... */;
SubmitterCompletable<Whatever> submitterCompletable = c -> runAsync(c, executor);
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 1
    Thanks, but this will also restrict the created `SubmitterCompletable` to certain `Callable`s, which is not what I want. To be more clear what I mean: Imagine if the `ExecutorService` interface had a generic type `T` like this, then you could only submit tasks with specific return values `T`. With that in mind, you're not answering my question, you're changing the rules to make it work. – stonar96 Dec 27 '21 at 19:49