Lambda Expression and Generic Defined Only in Method

Lambda Expression and generic defined only in method

You can't use a lambda expression for a functional interface, if the method in the functional interface has type parameters. See section §15.27.3 in JLS8:

A lambda expression is compatible [..] with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of [..] T. [..] A lambda expression is congruent with a function type if all of the following are
true:

  • The function type has no type parameters.
  • [..]

Java Lambda Expressions and Method References to Generic Methods

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;

How to create lambda expression for functional interface having generic method

Generics at method scope cannot be used in lambda expressions. It will throw

Illegal lambda expression: Method modify of type MyInterface is generic

You need to set the generic at class scope.

@FunctionalInterface
interface MyInterface<T> {
T modify(Object obj);
}

Then use it as follows:

MyInterface obj2 = o -> {return o;};

Can Lambda Expressions only be used in the same Method they're defined in (Java)?

The type defined by a lambda expression can in theory be used anywhere, so I believe that your question stems from the fact that you've only seen limited examples.

I've worked on several codebases where methods returned implementations of functional interfaces specified by lambda expressions. I can give a simple example:

public class A {
public static Predicate<String> startsWithA() {
return input -> input.startsWith("A");
}
}

public class B {
public static void main(String[] args) {
System.out.println(A.startsWithA().test("Aardvark"));
}
}

Generic abstract method & Functional interface & lambda expression

If I use a lambda expression with functional interface, the abstract method in functional interface cannot have "independent" generic parameter type on its own?

Correct. Your example 3 demonstrates this. (Easier terminology here would be "the abstract method cannot be generic": generic methods are methods definining their own type parameters).

You can't do it with a lambda, you can do it with a method reference.

interface MyInterface<T> {
<T> T test(T t);
}

static <T> T foo(T t) { return t; }

public static void main (String[] args) throws java.lang.Exception
{
MyInterface<?> x = Ideone::foo; // Compiles OK
MyInterface<?> y = a -> a; // Error.
}

Ideone demo

You found the thing about lambdas already. Don't know if method references are useful to you.

How to write a generic method for lambda?

You should change your definition to

public interface Mapper<T> { // type bound to the interface
T map(T element);
}

and then use it as :

Mapper<Integer> mapper = element -> element * 2; // notice Integer and not 'int' for the type

which can also be written as :

Mapper<Integer> mapper = (Integer element) -> element * 2;

Lambda expression with generic

You are declaring several type parameters without using them and perhaps not even understanding that these are independent type parameters despite having the same name. And when you declare a class with type parameters, you have to declare type arguments when using that class.

So when you define a class like

public class Test<T,U> {
public static <T,U> void main(String[] args) { … }
private T operate(MyGen choice, U... args) { … }
interface MyGen<T,U> {
public T asYouWish(U... args);
}
}

it’s not different to declaring

public class Test<T,U> {
public static <A,B> void main(String[] args) { … }
private T operate(MyGen choice, U... args) { … }
interface MyGen<X,Y> {
public X asYouWish(Y... args);
}
}

as naming them all T and U does not create a relationship but is only confusing a human reader. The type parameters of a class affect its non-static members only and the only non-static member of Test is the method operate, which refers to MyGen without actual type arguments.

A correct usage would be

private T operate(MyGen<T,U> choice, U... args) {
return choice.asYouWish(args);
}

so you are declaring that the MyGen parameter must be parametrized accordingly to the Test’s parametrization, which now enables you to invoke asYouWish without warnings and without type casts.

It should be emphasized that you can’t use the same instance of Test to use the method for Strings and Integers as you define the type arguments for T and U when instantiating Test.

Further, the type parameters of the main method are independent of the Test’s type parameters and since they are not used in the method’s signature, quite useless. Referring to them within the lambda expression is pointless, even if there was no compiler error within the lambda expression’s body, there are no instances of that type in scope to use the function.

Putting it together, you get

public class Test<T,U> {

public static void main(String[] args) {
Str myHello = (message1, message2) ->
System.out.println("Hello " + message1 + message2);
myHello.welcome("Mr. ", "rickroll");

{
Test<Integer,Integer> myTest = new Test();
MyGen<Integer,Integer> addition = operationArgs -> {
int sum = 0;
for(int i: operationArgs) sum += i;
return sum;
};
System.out.println("2+3+4+5 = " + myTest.operate(addition, 2, 3, 4, 5));
}
{
Test<String,String> myTest = new Test();
MyGen<String,String> addition = operationArgs -> {
String sum = "";
for(String i: operationArgs) sum += i;
return sum;
};
System.out.println("A.....gl ?"
+ myTest.operate(addition, "b", "c", "d", "e", "f"));
}
}
private T operate(MyGen<T,U> choice, U... args) {
return choice.asYouWish(args);
}
interface Str {
public void welcome(String a, String b);
}
interface MyGen<X,Y> {
public X asYouWish(Y... args);
}
}

You can get rid of the requirement to parametrize the Test instance, by simply turning the operate into a generic static method as the instance is not needed anyway. If you want to define a generic MyGen function, you have to abstract the + operator, as unlike its appearance in the String case, there is no operator overloading in Java.

Further, you have to implement it without needing a neutral element, as 0 doesn’t magically turn into the empty string, when attempting to use the same function with strings.

import java.util.function.BinaryOperator;

public class Test {

public static void main(String[] args) {
Str myHello = (message1, message2) ->
System.out.println("Hello " + message1 + message2);
myHello.welcome("Mr. ", "rickroll");

example(Integer::sum, "2+3+4+5 = ", 2, 3, 4, 5);
example(String::concat, "A.....gl ?", "b", "c", "d", "e", "f");
}
private static <E> void example(BinaryOperator<E> plus, String head, E... values) {
MyGen<E,E> addition = args -> {
E sum=args[0];
for(int ix=1; ix<args.length; ix++) sum = plus.apply(sum, args[ix]);
return sum;
};
System.out.println(head + Test.operate(addition, values));
}
private static <T,U> T operate(MyGen<T,U> choice, U... args) {
return choice.asYouWish(args);
}
interface Str {
public void welcome(String a, String b);
}
interface MyGen<X,Y> {
public X asYouWish(Y... args);
}
}

This is only for demonstration purposes as that work has already been made, e.g. you could change the example method to:

private static <E> void example(BinaryOperator<E> plus, String head, E... values) {
MyGen<E,E> addition = args -> Arrays.stream(args).reduce(plus).get();
System.out.println(head + Test.operate(addition, values));
}


Related Topics



Leave a reply



Submit