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

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().

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.

Using method references on a instance to be determined at runtime in Java

I was testing out rules of using method references, but the code I wrote would not compile. The compiler keeps giving telling me that I cannot reference a non-static method from a static context. However, in the Java Documents it explicitly wrote that it is possible to use "::" to "reference to an instance method of an arbitrary object of a particular type". Can anyone point out what's wrong with my code? Thank you!
package Test;
import java.util.function.BiPredicate;
class Evaluation {
public boolean evaluate(int a, int b) {
if (a-b ==5){
return true ;
}
return false;
}
public void methodTest() {
BiPredicate<Integer, Integer> biPredicate = Evaluation::evaluate;
System.out.println(biPredicate.test(6,1));
}
}
Edit: After reading the answers, I was wondering if it is the case that referencing an instance method by the class name only works in some functional interfaces but not in other ones? For instance,
BiPredicate <String, Integer> biPredicate = String::startsWith;
doesn't compile, while:
Predicate <String> predicate = String::isEmpty;
compiles.
If this is the case, is there a page/tutorial/whatever that anyone can refer me to that explains which function interfaces are compatible and which are not?
When statically referencing an instance method, the returned functor takes an additional argument that represents the instance.
interface Func {
boolean evaluate(Evaluation instance, int a, int b);
}
...
Func biPredicate = Evaluation::evaluate;
System.out.println(biPredicate.evaluate(new Evaluation(), 6, 1));
But you will need to pass an instance of Evaluation when calling it.
Since your evaluate method does not use any instance fields, you might as well make it static, then you don't need to pass an instance, and can use just a BiPredicate<Integer, Integer> like you tried to.
If your method is an instance method, then you have to invoke it on some instance, for example:
public void methodTest(){
BiPredicate<Integer, Integer> biPredicate = this::evaluate;
System.out.println(biPredicate.test(6,1));
}
Since you are not using any instance variables or method, you can simply make it static and keep it like it is.
I'm still trying to figure out the rule that applies, but the problem goes away if you use
BiPredicate<Integer, Integer> biPredicate = this::evaluate;
I'm puzzling through https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13 but as near as I can figure, because the Evaluation::evaluate forces the compiler to create an arbitrary object of the type Evaluation, and you're calling it from within an object of that type, that the rule is different. You need to call it from the specific object inside of which the methodTest method appears.
While I don't have the explanation, the solution is to use this::evaluate. That unambiguously ties the method reference to the object calling it.
Side note: You don't need to evaluate a boolean as a conditional in order to derive a boolean from the boolean. You could just return a - b == 5;.
I am probably way too late to answer this, but since the question is still unanswered I would like to attempt an answer.
I think there is a miss in what OP is trying to achieve.
I understand that OP is trying to understand, why something like this would work:
String str = "abc";
Predicate<String> methodRef = str::startsWith;
methodRef.test("s");
and then,
Predicate <String> predicate = String::isEmpty
Works and in similar fashion, why wouldn't
Predicate <String> predicate = String::startsWith;
Compile which is taking String class name compile.
That is simply because, Predicate basically, takes any argument and returns a boolean. This is not a correct setup for this problem.
You can instead try,
BiFunction<String, String, Boolean> methodRef2 = String::startsWith;
methodRef2.apply("sdsdfsd", "sdfsdf");
This would work, as startswith needs a source string, string to check and return value. Basically there are 4 ways to invoke method references in Java 8
Static method calls.
Instance method calls.
Class method calls
Constructors

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.

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

Removing compiler's warning from a method call, advise needed

As part of my Utils class, i have both
public static boolean isStringEmptyOrNull(String... s) {
When testing for a null condition
assertTrue(Utils.isStringEmptyOrNull(null));
I get "The argument of type null should explicitly be cast to String[] for the invocation of the varargs method isStringEmptyOrNull(String...) from type Utils. It could alternatively be cast to String for a varargs invocation" warning.
I'd rather not case anything though. This test is designed to simulate a condition where argument passed to the method is a null.
Is there a way to remove this warning without changing the signature of the method?
You should probably test both of these cases:
assertTrue(Utils.isStringEmptyOrNull(new String[] { null }));
assertTrue(Utils.isStringEmptyOrNull((String[]) null));
... although it's not clear to me why a method which sounds like it should only take a single string is taking an array of strings in the first place, to be honest.
(Is it obvious to you which of those invocations you meant without the cast? It isn't obvious to me... I'd have to look it up to check.)
If you are trying to mimic the way a client of your library function would call your code, you should take advantage of the fact that they will not call this particular method with the literal "null" (what would be the point?)
Instead, they would pass in some variable or expression. Since that's the case, you can do something like this and avoid casting:
String nullString = null;
assertTrue(Utils.isStringEmptyOrNull(nullString));
You could specifically tell the compiler to ignore the warning using #SuppressWarnings("all").
Well that warning's there for a reason: when you call your method with null argument, since null is all and any type in Java (including Array), the compiler effectively will not know if you're calling the var-args method with an array or a non-array object (each of which is treated differentlly when var-args arguments are used). What you can do is annotate the method with SuppressWarnings("All") and then test for null before doing anything with the argument(s)
Change your test like so:
String nullString = null;
assertTrue(Utils.isStringEmptyOrNull(nullString));
Rather curious why you use a vararg method for this in the first place though...

Categories

Resources