0

The code should not compile but it does!

public class MyClass {

...........

    private void setEndDateOfValidStatusToCurrentTime(List<LifecycleStatus> oldStatuses, Date currentTime)
    {
        oldStatuses.stream()
            .filter(oldStatus -> isValidNow(oldStatus, currentTime))
            .findFirst().ifPresent(oldStatus -> oldStatus.setValidToDate(currentTime));
    }

    private boolean isValidNow(LifecycleStatus lifecycleStatus, Date currentTime)
    {
        Date start = lifecycleStatus.getValidFromDate();
        Date end = lifecycleStatus.getValidToDate();
        Date startTime = Optional.ofNullable(start).orElse(new Date(0L)); // BEGINNING OF TIME
        Date endTime = Optional.ofNullable(end).orElse(new Date(Long.MAX_VALUE)); // END OF TIME

        return startTime.before(currentTime) && endTime.after(currentTime);
    }

}

Reason: I am using isValidNow() in a lambda in order to target the Predicate interface since the filter method requires it. But isValidNow() is a 2-argument method and test() in Predicate expects only 1 argument!

I do know the Java compiler has the power of type inference. With such power, perhaps the smart compiler internally breaks down isValidNow(), determines that it can safely put aside the second argument (currentTime) and come up with an implementation that satisfies test() in Predicate by using only the first argument (oldStatus).

Then why doesn't type inference work when I try to use a method reference instead? Interestingly, if I replace

filter(oldStatus -> isValidNow(oldStatus, currentTime)) 

with

filter(this::isValidNow) 

I see these compiler errors:

- The method filter(Predicate<? super LifecycleStatus>) in the type Stream<LifecycleStatus> is not applicable for the arguments 
 (this::isValidNow)
- MyClass does not define isValidNow(LifecycleStatus) that is applicable here
softwarelover
  • 1,009
  • 1
  • 10
  • 22

1 Answers1

3

oldStatus -> isValidNow(oldStatus, currentTime) is the predicate/lambda here, and only takes one argument. That lambda is effectively equivalent to:

new Predicate<LifecycleStatus> {
    boolean test(LifecycleStatus oldStatus) {
        return isValidNow(oldStatus, currentTime);
    }
}

(where currentTime is a local variable from the enclosing scope.)

That's certainly not the same as this::isValidNow, which is why the latter doesn't compile.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Are you saying, in order to comply with the one-argument Predicate method test(T t), on the right side of the lambda expression I can use a method with as many arguments as I want, as long as the left side has only one? – softwarelover Nov 16 '17 at 19:51
  • @softwarelover - A lambda is a mapping between (in this case) one argument and an expression. That expression can be anything you want (providing the type is correct). – Oliver Charlesworth Nov 16 '17 at 19:52
  • I see in your equivalent code for Predicate implementation that oldStatus is being passed as an argument. What about currentTime? The test() method invokes isValidNow() but how does test() know about currentTime? I don't see any declaration of currentTime. Please clarify. – softwarelover Nov 16 '17 at 19:56
  • @softwarelover - Probably worth reviewing this: https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html (in particular the "Accessing local variables of enclosing scope..." section). – Oliver Charlesworth Nov 16 '17 at 19:57
  • thanks but kindly put currentTime somewhere in your code to make it complete. – softwarelover Nov 16 '17 at 19:59
  • @softwarelover - All I'm saying with that code snippet is that it's exactly the same as the lambda (in the context of your code). `currentTime` comes from wherever it comes from in your code. – Oliver Charlesworth Nov 16 '17 at 20:01
  • @softwarelover - Excellent ;) – Oliver Charlesworth Nov 16 '17 at 20:04
  • Oliver, could you kindly say why this::isValidNow is failing to compile? I have seen that whenever I use a method reference in a working code, behind the schene JRE creates a lambda for it. So, I wonder why the compiler cannot accept a method reference in this case since the compiler does employ a lambda (hidden). – softwarelover Nov 16 '17 at 20:20
  • @softwarelover - Because it doesn't satisfy the requirements for the `filter` method - i.e. it doesn't match the `Predicate.test` interface. – Oliver Charlesworth Nov 16 '17 at 20:39