How lambda expression initialize parameter? - java

I'm confused with lamda expression.
JavaDStream<ConsumerRecord<String, String>> rsvpsWithGuestsStream =
meetupStream.filter(f -> !f.value().contains("\"guests\":0"));
rsvpsWithGuestsStream.foreachRDD((JavaRDD<ConsumerRecord<String, String>> r) -> {
MongoSpark.save(
r.map(
e -> Document.parse(e.value())
)
);
});
Here is a foreachRDD method void foreachRDD(VoidFunction<R> foreachFunc), It accepts a functional interface.
And in code, JavaRDD<ConsumerRecord<String, String>> r passed as a argument which is internally used by its call method.
I want to know Does lambda expression initialize r on its own ? Becasue it can call map only if its initialized. And In code I cant see anywhere its already created.
Can anyone help me to understand this ?

A lambda expression does not initialize anything. It is just an anonymous function which does something with its arguments - assuming they have been already initialized. I am not familiar with your use case (guess it is Spark) but it looks like r is just one of the elements in the stream, and each such element is passed in to your lambda. Below is a similar but much simpler example:
Stream.of("a", "b", "c").forEach(el -> System.out.println(el));
Here el is each of the elements "a", "b" and "c", passed in to the lambda which simply prints it. Just like a function, a lambda doesn't know anything about its arguments and whether they are initialiazed - this is up to the caller, in the above cases - the forEach methods.

Here is a quote from Java spec:
15.27.1. Lambda Parameters
When the lambda expression is invoked (via a method invocation
expression (§15.12)), the values of the actual argument expressions
initialize newly created parameter variables, each of the declared or inferred type, before execution of the lambda body. The Identifier
that appears in the LambdaParameter or directly in the
LambdaParameterList or LambdaParameters may be used as a simple name
in the lambda body to refer to the formal parameter.
https://docs.oracle.com/javase/specs/jls/se15/html/jls-15.html#jls-15.27.1

Related

Method reference - Difference between "Reference to a static method" and "Reference to an instance method of an arbitrary object of a particular type"

I've learned that there are 4 kinds of types in method reference. But I don't understand the difference between "Reference to a static method" and "Reference to an instance method of an arbitrary object of a particular type".
For example:
List<String> weeks = new ArrayList<>();
weeks.add("Monday");
weeks.add("Tuesday");
weeks.add("Wednesday");
weeks.add("Thursday");
weeks.add("Friday");
weeks.add("Saturday");
weeks.add("Sunday");
weeks.stream().map(String::toUpperCase).forEach(System.out::println);
The method toUpperCase is not a static method... so why can one write in the way above, rather than needing to use it this way:
weeks.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
Explanation
The method toUpperCase is not a static method... so why can one write in the way above, rather than needing to use it this way:
weeks.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
Method references are not limited to static methods. Take a look at
.map(String::toUpperCase)
it is equivalent to
.map(s -> s.toUpperCase())
Java will just call the method you have referenced on the elements in the stream. In fact, this is the whole point of references.
The official Oracle tutorial explains this in more detail.
Insights, Examples
The method Stream#map (documentation) has the following signature:
<R> Stream<R> map​(Function<? super T, ? extends R> mapper)
So it expects some Function. In your case this is a Function<String, String> which takes a String, applies some method on it and then returns a String.
Now we take a look at Function (documentation). It has the following method:
R apply​(T t)
Applies this function to the given argument.
This is exactly what you are providing with your method reference. You provide a Function<String, String> that applies the given method reference on all objects. Your apply would look like:
String apply(String t) {
return t.toUpperCase();
}
And the Lambda expression
.map(s -> s.toUpperCase())
generates the exact same Function with the same apply method.
So what you could do is
Function<String, String> toUpper1 = String::toUpperCase;
Function<String, String> toUpper2 = s -> s.toUpperCase();
System.out.println(toUpper1.apply("test"));
System.out.println(toUpper2.apply("test"));
And they will both output "TEST", they behave the same.
More details on this can be found in the Java Language Specification JLS§15.13. Especially take a look at the examples in the end of the chapter.
Another note, why does Java even know that String::toUpperCase should be interpreted as Function<String, String>? Well, in general it does not. That's why we always need to clearly specify the type:
// The left side of the statement makes it clear to the compiler
Function<String, String> toUpper1 = String::toUpperCase;
// The signature of the 'map' method makes it clear to the compiler
.map(String::toUpperCase)
Also note that we can only do such stuff with functional interfaces:
#FunctionalInterface
public interface Function<T, R> { ... }
Note on System.out::println
For some reason you are not confused by
.forEach(System.out::println);
This method is not static either.
The out is an ordinary object instance and the println is a non static method of the PrintStream (documentation) class. See System#out for the objects documentation.
Method reference quite intelligent feature in Java. So, when you use non-static method reference like String:toUpperCase Java automatically comes to know that it needs to call toUpperCase on the on the first parameter.Suppose there is two parameter a lambda expression expect then the method will call on the first parameter and the second parameter will pass as an argument of the method. Let' take an example.
List<String> empNames = Arrays.asList("Tom","Bob");
String s1 = empNames.stream().reduce("",String::concat); //line -1
String s2 = empNames.stream().reduce("",(a,b)->a.concat(b)); // line -2
System.out.println(s1);
System.out.println(s2);
So, on above example on line -1, String#concat method will call on the first parameter (that is a line-2) and a second parameter (that b for line -2) will pass as the argument.
It is possible for the multiple arguments (more than 2) method also but you need to very careful about the which sequence of the parameters.
I highly recommend you to read the Oracle's article about method references: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
That is the form of a lambda expression:
s->s.toUpperCase()
And that is a method reference:
String::toUpperCase
Semantically, the method reference is the same as the lambda expression, it just has different syntax.

method reference vs lambda expression

I want to replace lambda expression by method reference in the below example :
public class Example {
public static void main(String[] args) {
List<String> words = Arrays.asList("toto.", "titi.", "other");
//lambda expression in the filter (predicate)
words.stream().filter(s -> s.endsWith(".")).forEach(System.out::println);
}
}
I want to write a something like this :
words.stream().filter(s::endsWith(".")).forEach(System.out::println);
is it possible to transform any lambda expression to method reference.
There is no way “to transform any lambda expression to method reference”, but you can implement a factory for a particular target type, if this serves recurring needs:
public static <A,B> Predicate<A> bind2nd(BiPredicate<A,B> p, B b) {
return a -> p.test(a, b);
}
with this, you can write
words.stream().filter(bind2nd(String::endsWith, ".")).forEach(System.out::println);
but actually, there’s no advantage. Technically, a lambda expression does exactly what you want, there’s the minimum necessary argument transformation code, expressed as the lambda expression’s body, compiled into a synthetic method and a method reference to that synthetic code. The syntax
s -> s.endsWith(".") also is already the smallest syntax possible to express that intent. I doubt that you can find a smaller construct that would still be compatible with the rest of the Java programming language.
You can use selectWith() from Eclipse Collections. selectWith() takes a Predicate2 which takes 2 parameters instead of a Predicate. The second parameter to selectWith() gets passed as the second parameter to the Predicate2 every time it's called, once per item in the iterable.
MutableList<String> words = Lists.mutable.with("toto.", "titi.", "other");
words.selectWith(String::endsWith, ".").each(System.out::println);
By default Eclipse Collections is eager, if you want to iterate lazily then you can use asLazy()
words.asLazy().selectWith(String::endsWith, ".").each(System.out::println);
If you can't change from List:
List<String> words = Arrays.asList("toto.", "titi.", "other");
ListAdapter.adapt(words).selectWith(String::endsWith, ".").each(System.out::println);
Eclipse Collections' RichIterable has several other *With methods which work well with method references, including rejectWith(), partitionWith(), detechWith(), anySatisfyWith(), allSatisfyWith(), noneSatisfyWith(), collectWith()
Note: I am a contributor to Eclipse Collections.

Why Java 8 Stream forEach method behaves differently?

As per my understanding of java 8 lambda expressions, if we don't include code after "->" in curly braces that value will be returned implicitly. But in case of below example, forEach method expects Consumer and expression returns value but the compiler is not giving an error in Eclipse.
List<StringBuilder> messages = Arrays.asList(new StringBuilder(), new StringBuilder());
messages.stream().forEach(s-> s.append("helloworld"));//works fine
messages.stream().forEach((StringBuilder s)-> s.append("helloworld")); //works fine
messages.stream().forEach(s-> s); // doesn't work , Void methods cannot return a value
messages.stream().forEach(s-> s.toString()); // works fine
messages.stream().forEach(s-> {return s.append("helloworld");}); // doesn't work , Void methods cannot return a value
messages.stream().forEach((StringBuilder s)-> {return s.append("helloworld");}); // doesn't work , Void methods cannot return a value
s.append returns StringBuilder and s.toString() returns String but lambda treats it as void.
What am I missing here? Why isn't the compiler giving an error when we invoke method on object?
From JLS 15.27.3. Type of a Lambda Expression:
A lambda expression is congruent with a function type if all of the
following are true:
The function type has no type parameters.
The number of lambda parameters is the same as the number of parameter types of the function type.
If the lambda expression is explicitly typed, its formal parameter types are the same as the parameter types of the function type.
If the lambda parameters are assumed to have the same types as the function type's parameter types, then:
If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
If the function type's result is a (non-void) type R, then either i) the lambda body is an expression that is compatible with R
in an assignment context, or ii) the lambda body is a value-compatible
block, and each result expression (§15.27.2) is compatible with R in
an assignment context.
The highlighted sentence above means that any statement lambda expression (i.e. a lambda expression without a block) matches a functional interface whose single method's return type is void (such as the Consumer functional interface required by the forEach method).
This explains why s.append("helloworld") & s.toString() (your 1,2 & 4 examples) work fine as statement lambda expressions.
examples 5 & 6 don't work, since those have block lambda bodies which are value-compatible lambda expressions. To be void-compatible, all the return statements must return nothing (i.e. just return;).
On the other hand, the following void-compatible block lambda bodies will pass compilation :
messages.stream().forEach(s-> {s.append("helloworld");});
messages.stream().forEach(s-> {s.append("helloworld"); return;});
Your 4th example - messages.stream().forEach(s-> s); doesn't work for the same reason the following method doesn't pass compilation :
void method (StringBuilder s)
{
s;
}
From java.util.stream.Stream, the signature of forEach is:
void forEach(Consumer<? super T> action)
From java.util.function.Consumer, the action must implement the following method:
void accept(T t)
In all your examples that don't work, you're returning a T, which doesn't match the return type of void.
Why compiler is not giving error when we invoke method on object?
Because you're not trying to return something, the lambda's return type is void, which matches the required Consumer signature.
The only possible type for s -> s is T -> T, whereas (StringBuilder s) -> s.append() could be StringBuilder -> (), which satisfies the void requirement.
Stream.forEach(Consumer) takes a consumer which implements one method
void accept(Object o);
i.e. it can't return anything.
If you want to return something you have to do something with the value returned.
You said
if we don't include code after "->" in curly braces that value will be returned implicitly
But that's not quite accurate. Rather, if you do not include curly braces, then, if a value could be be returned, then it will be returned implicitly. However, if a value should not be returned, because, for example, the functional interface method has a void return value, then the compiler will see that and not attempt to implicitly return anything.
In this case, forEach accepts a Consumer, whose return type is void, which is why you are getting compile errors when you attempt to return a value from it explicitly.

Sonar : Replace this lambda with a method reference

This code sample
Collection<Number> values = transform(
getValuatedObjects(),
input -> getValueProvider().apply(input).getValue());
violates the Sonarqube rule:
Lambdas should be replaced with method references
Is it a sonar bug?
Or can I really use a method reference?
You can’t replace the lambda input -> getValueProvider().apply(input).getValue() with a method reference without changing the semantics.
A method reference replace a single method invocation, so it can’t simply replace a lambda expression consisting of more than one method invocation.
A lambda expression of the form input -> getValueProvider().apply(input) could be replaced by getValueProvider()::apply if, and only if, the evaluation time of getValueProvider() does not matter as in the lambda form the method is invoked on each lambda body evaluation while for the method reference it is invoked only once and the result captured.
This is similar to the difference between x -> System.out.println(x) and System.out::println where reading the contents of the field System.out happens at different times but usually it doesn’t matter. But you should be aware of the difference.
In your example, a third method getValue() is invoked. The only way to express that with method references needs a functional interface like Function which has methods like andThen and/or compose. However, the way Java 8 works, that would require casting the first method reference to the target interface to invoke the combining method which would be by no way easier to read that the lambda expression you have now: ((Function<X,Y>)getValueProvider()::apply).andThen(Y::getValue) where Y is the type, apply(input) returns.
Note that the rule says “Replace lambdas with method references when possible” which gives you room to say, “well, here it is impossible”, however, I’m not sure how much you can call it a “rule” then…
list.stream().sorted().collect(Collectors.toList()).forEach(element ->
operate(element));
replace the above lambda with a method reference.
list.stream().sorted().collect(Collectors.toList()).forEach(this::operate);
if you are coding in java 8 you can use method reference in place of lambda expression for code readable
List<Integer> list = Arrays.asList(1,2,3,4,5);
replace this lambda with a method reference
strList.stream().sorted().collect(Collectors.toList()).forEach(s -> System.out.println(s));
Replace
strList.stream().sorted().collect(Collectors.toList()).forEach(System.out::println);
List<String> inputStringList = List.of("A", "B", "C", "D");
List<String> outputStringList = List.of("C", "D", "E", "F");
assertTrue(outputStringList.stream().anyMatch(inputStringList::contains));
assertTrue(outputStringList.stream().allMatch(inputStringList::contains));
This is a sample code to check if content of List is matching against another List using method reference.

Comparator.reversed() does not compile using lambda

I have a list with some User objects and i'm trying to sort the list, but only works using method reference, with lambda expression the compiler gives an error:
List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error
Error:
com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
userList.sort(Comparator.comparing(u -> u.getName()).reversed());
^
symbol: method getName()
location: variable u of type Object
1 error
This is a weakness in the compiler's type inferencing mechanism. In order to infer the type of u in the lambda, the target type for the lambda needs to be established. This is accomplished as follows. userList.sort() is expecting an argument of type Comparator<User>. In the first line, Comparator.comparing() needs to return Comparator<User>. This implies that Comparator.comparing() needs a Function that takes a User argument. Thus in the lambda on the first line, u must be of type User and everything works.
In the second and third lines, the target typing is disrupted by the presence of the call to reversed(). I'm not entirely sure why; both the receiver and the return type of reversed() are Comparator<T> so it seems like the target type should be propagated back to the receiver, but it isn't. (Like I said, it's a weakness.)
In the second line, the method reference provides additional type information that fills this gap. This information is absent from the third line, so the compiler infers u to be Object (the inference fallback of last resort), which fails.
Obviously if you can use a method reference, do that and it'll work. Sometimes you can't use a method reference, e.g., if you want to pass an additional parameter, so you have to use a lambda expression. In that case you'd provide an explicit parameter type in the lambda:
userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());
It might be possible for the compiler to be enhanced to cover this case in a future release.
You can work around this limitation by using the two-argument Comparator.comparing with Comparator.reverseOrder() as the second argument:
users.sort(comparing(User::getName, reverseOrder()));
Contrary to the accepted and upvoted answer for which bounty has been awarded, this doesn't really have anything to do with lambdas.
The following compiles:
Comparator<LocalDate> dateComparator = naturalOrder();
Comparator<LocalDate> reverseComparator = dateComparator.reversed();
while the following does not:
Comparator<LocalDate> reverseComparator = naturalOrder().reversed();
This is because the compiler's type inference mechanism isn't strong enough to take two steps at once: determine that the reversed() method call needs type parameter LocalDate and therefore also the naturalOrder() method call will need the same type parameter.
There is a way to call methods and explicitly pass a type parameter. In simple cases it isn't necessary because it's inferred, but it can be done this way:
Comparator<LocalDate> reverseComparator = Comparator.<LocalDate>naturalOrder().reversed();
In the example given in the question, this would become:
userList.sort(Comparator.comparing<User, String>(u -> u.getName()).reversed());
But as shown in the currently accepted answer, anything that helps the compiler inferring type User for the comparing method call without taking extra steps will work, so in this case you can also specify the type of the lambda parameter explicitly or use a method reference User::getName that also includes the type User.
The static method Collections.reverseOrder(Comparator<T>) seems to be the most elegant solution that has been proposed. Just one caveat:
Comparator.reverseOrder() requires that T implements comparable and relies on the natural sorting order.
Collections.reverseOrder(Comparator<T>) has no restriction applied on type T

Categories

Resources