Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Predicate.isEqual implemented the way it is?

Tags:

java

java-8

Lately I've been fiddling around with the new java8 features to have a better understanding of them.

When trying some stuff with Stream.filter I came across the source of Predicate.java in which I found the following implementation of the isEqual method:

/**
 * Returns a predicate that tests if two arguments are equal according
 * to {@link Objects#equals(Object, Object)}.
 *
 * @param <T> the type of arguments to the predicate
 * @param targetRef the object reference with which to compare for equality,
 *               which may be {@code null}
 * @return a predicate that tests if two arguments are equal according
 * to {@link Objects#equals(Object, Object)}
 */
static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : object -> targetRef.equals(object);
}

What made me wonder was this line: : object -> targetRef.equals(object);.

Maybe I am massively overthinking this, but I couldn't help immediately thinking why that line wasn't : targetRef::equals; like this:

static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : targetRef::equals;
}

Seems to me a unnecessary creation of a lambda otherwise.

Unless I'm missing something, to me those do the same. (They are the same right?) Is there any reason why the current implementation was chosen? Or is this just something incredibly small that it just got overlooked or nobody really cared.

Guess that actually results in a bonus question: Is there any benefit in using one way over the other? Like some kind of (probably very small) performance bonus?

like image 563
Olle Kelderman Avatar asked Oct 10 '16 11:10

Olle Kelderman


People also ask

Why do we use predicate in Java?

The predicate is a predefined functional interface in Java defined in the java. util. Function package. It helps with manageability of code, aids in unit-testing, and provides various handy functions.

What is the difference between a predicate integer and an IntPredicate?

Java IntPredicate interface is a predicate of one int-valued argument. It can be considered an operator or function that returns a value either true or false based on certain evaluation on the argument int value. IntPredicate is a functional interface whose functional method is boolean test(int a) .


1 Answers

Why was it done this way?

Pure speculation but, perhaps when it was written the developer was more comfortable with the -> and possibly :: didn't even work at that time. The libraries were being written while they were fixing bugs in the compiler.

There is no way to know and even the person who write it probably doesn't remember.


Whether you use a method reference or a closure syntax, the same number of objects is created in most cases (as in this example)

Guess that actually results in a bonus question: Is there any benefit in using one way over the other? Like some kind of (probably very small) performance bonus?

Using a method reference means one less method call. This could have an indirect impact on inlining as the number of levels is limited to 9 by default. For example

import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        Consumer<String> lambda = s-> printStackTrace(s);
        lambda.accept("Defined as a lambda");

        Consumer<String> methodRef = Main::printStackTrace;
        methodRef.accept("Defined as a method reference");
    }


    static void printStackTrace(String description) {
        new Throwable(description).printStackTrace();
    }
}

prints

java.lang.Throwable: Defined as a lambda
    at Main.printStackTrace(Main.java:15)
    at Main.lambda$main$0(Main.java:6)
    at Main.main(Main.java:7)

java.lang.Throwable: Defined as a method reference
    at Main.printStackTrace(Main.java:15)
    at Main.main(Main.java:10)

In the first case, the compiler has generated a method called Main.lambda$main$0 which contains the code which actually calls printStackTrace

Where using one or the other makes more difference is when you could have a capturing (saves a value) or non-capturing lambda. A non-capturing lambda is only created once.

e.g.

Consumer<String> print1 = System.out::println; // creates an object each time
Consumer<String> print2 = s->System.out.println(s); // creates an object once.

In the first case, if you call System.setOut it will ignore this change as it has it's own copy of the PrintStream to write to.

like image 62
Peter Lawrey Avatar answered Sep 27 '22 16:09

Peter Lawrey