Почему вывод типов не работает одинаково для лямбда-выражений и ссылок на методы в Java?

Код не должен компилироваться, но компилируется!

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);
    }

}

Причина: я использую isValidNow() в лямбда-выражении, чтобы ориентироваться на интерфейс Predicate, поскольку этого требует метод фильтра. Но isValidNow() — это метод с двумя аргументами, а test() в Predicate принимает только один аргумент!

Я знаю, что компилятор Java обладает способностью выводить типы. С такой мощью умный компилятор, возможно, внутренне разбивает isValidNow(), определяет, что он может безопасно отложить второй аргумент (currentTime) и предлагает реализацию, которая удовлетворяет test() в Predicate, используя только первый аргумент (oldStatus). .

Тогда почему не работает вывод типов, когда я пытаюсь вместо этого использовать ссылку на метод? Интересно, если я заменю

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

с

filter(this::isValidNow) 

Я вижу эти ошибки компилятора:

- 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

person softwarelover    schedule 16.11.2017    source источник


Ответы (1)


oldStatus -> isValidNow(oldStatus, currentTime) здесь является предикатом/лямбдой и принимает только один аргумент. Эта лямбда фактически эквивалентна:

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

(где currentTime — это локальная переменная из окружающей области.)

Это, конечно, не то же самое, что this::isValidNow, поэтому последний не компилируется.

person Oliver Charlesworth    schedule 16.11.2017
comment
Вы говорите, что для того, чтобы соответствовать методу Predicate с одним аргументом test(T t), в правой части лямбда-выражения я могу использовать метод с любым количеством аргументов, пока в левой части есть только один? - person softwarelover; 16.11.2017
comment
@softwarelover - лямбда - это сопоставление (в данном случае) одного аргумента и выражения. Это выражение может быть любым, что вы хотите (при условии, что тип правильный). - person Oliver Charlesworth; 16.11.2017
comment
Я вижу в вашем эквивалентном коде для реализации Predicate, что oldStatus передается в качестве аргумента. Как насчет текущего времени? Метод test() вызывает isValidNow(), но откуда test() узнает о currentTime? Я не вижу никакого объявления currentTime. Просьба уточнить. - person softwarelover; 16.11.2017
comment
@softwarelover - Вероятно, стоит просмотреть это: docs.oracle.com/javase/ tutorial/java/javaOO/ (в частности, раздел «Доступ к локальным переменным объемлющей области...»). - person Oliver Charlesworth; 16.11.2017
comment
спасибо, но, пожалуйста, поместите currentTime где-нибудь в свой код, чтобы сделать его завершенным. - person softwarelover; 16.11.2017
comment
@softwarelover - Все, что я хочу сказать этим фрагментом кода, это то, что он точно такой же, как лямбда (в контексте вашего кода). currentTime происходит из любого места в вашем коде. - person Oliver Charlesworth; 16.11.2017
comment
@softwarelover - Отлично ;) - person Oliver Charlesworth; 16.11.2017
comment
Оливер, не могли бы вы объяснить, почему this::isValidNow не компилируется? Я видел, что всякий раз, когда я использую ссылку на метод в рабочем коде, за схемой JRE создает для него лямбду. Итак, мне интересно, почему компилятор не может принять ссылку на метод в этом случае, поскольку компилятор использует лямбду (скрытую). - person softwarelover; 16.11.2017
comment
@softwarelover - потому что он не удовлетворяет требованиям метода filter, то есть не соответствует интерфейсу Predicate.test. - person Oliver Charlesworth; 16.11.2017