user3804927
user3804927

Reputation: 779

Clarifying how Lambda expression works in the official Java tutorial

I've read the tutorial over and over again and I just don't understand the last part of approach 6.

Spcifically this part:

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

As a result, the following method invocation is the same as when you invoked printPersons in Approach 3: Specify Search Criteria Code in a Local Class to obtain members who are eligible for Selective Service:

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

So if p is a generic type how does it know it is a Person? I just don't get how it can distinguish itself as a person and later invoke the Person methods - getGender() and getAge(). Does p in this case refer to the class object of the surrounding class?


UPDATE:

Based on the provided answers, is this correct?

The following code:

interface CheckPerson {
    boolean test(Person p);
}

class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

printPersons(roster, new CheckPersonEligibleForSelectiveService());

Is the same as:

import java.util.function // import the Predicate<T> interface

public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

Because:

  1. Predicate interface is already defined in java.util.function
  2. By declaring Predicate as Person in the method argument:
    1. we are implicitly implementing Predicate with a type of Person, and
    2. when invoking the method with a generic type T (or lack thereof), the type is implicitly inferred by the method as Person

If I am wrong, please let me know, so that I can update my understanding. I really want to get this right.

Upvotes: 1

Views: 99

Answers (3)

mikołak
mikołak

Reputation: 9705

In general: the parameter is simply inferred from the type signature of the relevant functional interface, including any additional limitations to the generic parameter.

In this case, we know that the second argument is a Predicate<T>. Since Predicate<T> is a functional interface with the method test(T t), we know that the type of p is T (and not Predicate<T>).

And since T is limited to Person by the signature of printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester), the actual type of p is Person.


RE: UPDATE - the answer is both "yes" and "no" :).

"Yes", in general the sequence of type inference could be described this way.

"No", for the following reasons:

  • in 2.2, I'd say it is better to state that the method limits the type of T to Person. The type person is simply the "common denominator" of what is allowed. For example: if the signature of the method would would contain:
    • Predicate<? extends JLabel> - then the argument type would effectively be JLabel.
    • Predicate<?> - then the argument type would effectively be Object.
  • it is better to state the the compiler infers something about types, rather than something is "implicitly defined" by something else than the compiler. You may regard it as a nitpick, but this switch has two important consequences:
    • it can save you from reasoning pitfalls of the form "it's not X that does N, it's Y".
    • very specifically, since the compiler is able to infer that a type of the Predicate<Person> is legal, there is no need to actually create a new anonymous class that implements a functional interface. Indeed, in Java 8's current JDK, that's what happens. Here's an article explaining the details.

Upvotes: 1

Stephen C
Stephen C

Reputation: 718798

Addressing the UPDATE questions only ...

1) Predicate interface is already defined in java.util.function

Correct

2) By declaring Predicate as Person in the method argument:

Incorrect.

You are actually declaring tester as Predicate<Person>. You are not declaring Predicate at all here. Predicate is a standard interface that it declared by the standard libraries.

2a) we are implicitly implementing Predicate with a type of Person

Correct ... sort of. You are explicitly implementing it. And the type of the lambda that is required here is explicitly specified ... by the method signature of printPersonsWithPredicate. Specifically, because you declared the formal parameter tester to have type Predicate<Person>.

2b) when invoking the method with a generic type T (or lack thereof), the type is implicitly inferred by the method as Person

Not in this case.

Upvotes: 0

Radiodef
Radiodef

Reputation: 37845

The lambda expression is a definition for the body of Predicate<Person>#test, which will take a Person as a parameter. We know this because printPersonsWithPredicate is declared to take a Predicate<Person>, which the lambda is being passed as an argument for.

The syntax of a lambda is that the left hand side of the -> is the parameter list for the method that is being defined:

(Person p) -> p.getGender() == Person.Sex.MALE
    && p.getAge() >= 18
    && p.getAge() <= 25

However, the compiler is able to determine that the singular parameter p of the lambda must be a Person so it can be omitted. Hence the shortened form p -> ....

An anonymous class near-equivalent to the lambda is shown in Approach #4.

Upvotes: 1

Related Questions