Java lambda returns lambda

I am trying to do what seems to be relatively basic in the new programming area of โ€‹โ€‹jdk8 functional programming, but cannot make it work. I have this working code:

import java.util.*; import java.util.concurrent.*; import java.util.stream.*; public class so1 { public static void main() { List<Number> l = new ArrayList<>(Arrays.asList(1, 2, 3)); List<Callable<Object>> checks = l.stream(). map(n -> (Callable<Object>) () -> { System.out.println(n); return null; }). collect(Collectors.toList()); } } 

It takes a list of numbers and creates a list of functions that can print them. However, explicit casting to Callable seems redundant. It seems to me, and IntelliJ. And we both agree that this should also work:

 List<Callable<Object>> checks = l.stream(). map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 

However, I get an error message:

 so1.java:10: error: incompatible types: cannot infer type-variable(s) R List<Callable<Object>> checks = l.stream().map(n -> () -> {System.out.println(n); return null;}).collect(Collectors.toList()); ^ (argument mismatch; bad return type in lambda expression Object is not a functional interface) where R,T are type-variables: R extends Object declared in method <R>map(Function<? super T,? extends R>) T extends Object declared in interface Stream 1 error 
+18
java lambda java-8 functional-programming
Nov 11 '14 at 19:21
source share
3 answers

You find yourself in a Java 8s character set restriction that applies to the receiver of a method call. While target typing works (in most cases) for parameter types, it does not work for the object or expression to which you call the method.

Here l.stream(). map(n -> () -> { System.out.println(n); return null; }) l.stream(). map(n -> () -> { System.out.println(n); return null; }) is the receiver of the call to the collect(Collectors.toList()) method collect(Collectors.toList()) , therefore the target type List<Callable<Object>> not considered for him.

It is easy to prove that nested lambda expressions work if the type of target is known, for example

 static <T> Function<T,Callable<Object>> toCallable() { return n -> () -> { System.out.println(n); return null; }; } 

works without problems and you can use it to solve your original problem as

 List<Callable<Object>> checks = l.stream() .map(toCallable()).collect(Collectors.toList()); 

You can also solve the problem by introducing a helper method that changes the role of the first expression from the method receiver to the parameter

 // turns the Stream s from receiver to a parameter static <T, R, A> R collect(Stream<T> s, Collector<? super T, A, R> collector) { return s.collect(collector); } 

and rewrite the original expression as

 List<Callable<Object>> checks = collect(l.stream().map( n -> () -> { System.out.println(n); return null; }), Collectors.toList()); 

This does not reduce the complexity of the code, but can be compiled without any problems. For me it is deja vu. When Java 5 and Generics came out, programmers had to repeat type parameters in new expressions, simply transferring the expression to the general method, proving that type inference does not present a problem. It took until Java 7 before programmers were allowed to skip this unnecessary repetition of type arguments (using the "diamond operator"). Now we have a similar situation, wrapping the call expression in another method, turning the receiver into a parameter, proves that this restriction is not necessary. Therefore, perhaps we will get rid of this limitation in Java 10 ...

+20
Nov 12 '14 at 9:55
source share
โ€” -

I have not yet delved into the exact rules of how type inference works with lambdas. From the point of view of a common language, however, it is not always possible to write language rules that allow the compiler to understand everything that we think. I was a compiler for the Ada-language compiler, and I am familiar with many of the problems associated with language design. Ada uses type inference in many cases (where the type of the construct cannot be determined without looking at the whole expression containing the construct, which I think also applies to this Java lambda expression). There are some language rules that cause compilers to reject certain expressions as ambiguous when, in theory, there is only one possible interpretation. One reason, if I remember correctly, is that someone found a case where a rule that would allow the compiler to figure out the correct interpretation would require the compiler to make 17 passes through the expression in order to interpret it correctly.

Thus, although we may think that the compiler "should" be able to understand something in a particular case, it is simply impossible to make impossible.

+2
Nov 11 '14 at 20:05
source share

I ran into the same problem and was able to solve it by explicitly specifying a generic parameter type on map as follows:

 List<Callable<Object>> checks = l.stream(). <Callable<Object>>map(n -> () -> { System.out.println(n); return null; }). collect(Collectors.toList()); 
+1
Oct 24 '17 at 19:33
source share



All Articles