Are stack traces less navigable when using method references vs lambdas? - java

I just performed a quick experiment in Eclipse.
public class StackTractTest {
static class Nasty {
public Integer toInt() {
if (1 == 1) throw new RuntimeException();
return 1;
}
}
#Test
public void methodReference() {
Stream.of(new Nasty())
.map(Nasty::toInt)
.findFirst();
}
#Test
public void lambda() {
Stream.of(new Nasty())
.map(n -> n.toInt())
.findFirst();
}
}
When the method-reference test fails, the trace begins
java.lang.RuntimeException
at com.example.StackTractTest$Nasty.toInt(StackTractTest.java:11)
at com.example.StackTractTest$$Lambda$1/1681433494.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
There is no reference back to the line on which the method reference is used although the end of the trace (not shown) does link back to line with findFirst on.
While the lamdba stacktrace begins
java.lang.RuntimeException
at com.example.StackTractTest$Nasty.toInt(StackTractTest.java:11)
at com.example.StackTractTest.lambda$0(StackTractTest.java:26)
at com.example.StackTractTest$$Lambda$1/1681433494.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
Which correctly identifies the lambda was used on line 26.
Is this a peculiarity of the Eclipse compiler or is this a general disadvantage of using method references that should be considered when choosing between them and a lambda?

No, this is how it is currently implemented.
Quoting a paper written by Brian Goetz about the translation of lambda expressions:
When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression
...
Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.
The only difference between your two stacktraces is that the one with the explicit lambda has this line added:
at com.example.StackTractTest.lambda$0(StackTractTest.java:26)
It is because the lambda was translated by javac into a newly generated method, and you can actually see in the stacktrace that this new method was lambda$0.
With a method-reference, it is not necessary to generate a new method because it directly references an existing method.

No - in fact you get more clarity.
The fact that the at com.example.StackTractTest.lambda$0(StackTractTest.java:26) line appears in the lambda version is reminding you that a lambda is created for this technique while using a method reference does not create anything extra.
The lambda is created at run-time, the method-reference can be constructed at compile time.

Related

How to get 100% of coverage on lambda method [JAVA]

I tried everything to get 100% of coverage on this lambda method but no matter what I do I don't get it.
private String createMessage(List<FieldError> erros) {
return erros.stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (keyOld, keyNew) -> keyOld)).toString();
}
The uncovered code is the implementation of a lambda expression. You only have one lambda expression (keyOld, keyNew) -> keyOld, which means that that code doesn't get executed.
The lambda expression is the 3rd argument to Collectors.toMap(), i.e. BinaryOperator<U> mergeFunction, which is documented as "a merge function, used to resolve collisions between values associated with the same key".
If there are no collisions in the data, the lambda expression won't get executed, so make sure you test the code with data where the erros list contains 2 or more elements with the same getField() value.

How to pass parameter in Supplier function with method reference operator(::)

Sorry, it seems to be very basic in functional programming but I am not getting this idea. Actually I have a method in my code which consumes a method and another param as a parameter.
private <R> CompletableFuture<R> retryRequest(Supplier<CompletableFuture<R>> supplier, int maxRetries)
I want to call this function and pass another method(anOtherMethod) which taking one integer parameter:
CompletableFuture<Boolean> retry = this.retryRequest(this:: anOtherMethod, 2);
Not getting this how I can call this retryRequest and give anOtherMethod(123)?
I know it can work like this:
CompletableFuture<Boolean> retry = this.retryRequest(()-> anOtherMethod(123), 2);
You cannot instantiate a lambda with a specific captured value like 123 in the pure method reference variant.. You need to write the explicit lambda version with arrow, if you want to pass captured values other than the instance to execute the method on. Read more on capturing values in lambdas in this answer: Enhanced 'for' loop and lambda expressions
The only exception is an object, which itself becomes the first parameter.
Assume a signature that expects a Consumer of a String:
public void something(Consumer<String> job) {
...
The above signature will enable you to write the following calls:
String myString = " Hey Jack ";
something(myString::trim);
something(s -> s.trim());
Both do the same, and this is maybe unintuitive, because one takes an argument (the instance reference myString) and one seem not to (but it actually does, too). This works, because the compiler tries two possible resolutions for a lambda method reference (the above version with ::). On one hand, the compiler can apply signatures, as if the called method did not have any parameters, and none need passing. This is the case for myString.trim. But the compiler will also check, whether there is a static method String.trim(myString) (which luckiely there is not). If you wanted to call a static method without any parameters, then you'd have to call the class identifier with the function reference like so:
something(String::trim); // this version of trim does not exist.
This is sometimes even a problem, because if a class offers a static version of a method and an instance-related one, you get ambiguity:
public void somethingElse(Function<Integer, String> transformation) {...}
// This will not compile:
somethingElse(Integer::toString);
The above example will not compile, because the toString method exists twice, once as static Integer.toString(someInt) and once as instance related someInteger.toString().

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.

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.

Categories

Resources