Search code examples
javagenericslambdamethod-referencefunctional-interface

Java Lambda Expressions and Method References to Generic Methods


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?


Solution

  • 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;