Есть ли разница между объектами:: nonNull и x → x!= Null?

Рассмотрим следующий класс:

import java.util.Objects;
import java.util.function.Predicate;

public class LambdaVsMethodRef {
    public static void main(String[] args) {
        Predicate<Object> a = Objects::nonNull;
        Predicate<Object> b = x -> x != null;
    }
}

Первый предикат создается из справочника метода, а другой - лямбда-выражения. Эти предикаты имеют одинаковое поведение (тело nonNull просто return obj != null;). Лямбда на два символа короче (возможно, позволяет поточному конвейеру соответствовать одной строке).

Помимо стиля кода, существует ли разница между Objects::nonNull и x -> x != null? Положите другой путь, должен ли я отдать предпочтение другому?

В сообщениях списка рассылки lambda-dev и lambda-libs-spec- {наблюдатели, эксперты}, в которых упоминаются isNull, nonNull и isNotNull (раннее название), не рассматривались. (Я удивлен, что никто не задавался вопросом о добавлении методов Objects, поскольку они тривиально заменяются лямбдой, но, с другой стороны, так это Integer::sum.)

Я также посмотрел на байт-код с javap. Единственное различие заключалось в том, что дескриптор метода был передан методу лямбда-метафайла bootstrap:

  BootstrapMethods:
0: #16 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #17 (Ljava/lang/Object;)Z
    #18 invokestatic java/util/Objects.nonNull:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z
1: #16 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #17 (Ljava/lang/Object;)Z
    #20 invokestatic LambdaVsMethodRef.lambda$main$1:(Ljava/lang/Object;)Z
    #17 (Ljava/lang/Object;)Z

Конечно, метафакультет мог бы делать разные вещи для ссылок на методы и лямбда, по прихоти JVM, так что это не очень много.

Ответ 1

Как вы отметили, семантика лямбда x -> x != null и ссылка метода Objects::nonNull практически идентичны. Мне трудно придумать какую-либо реальную наблюдаемую разницу, не врываясь в класс, используя отражение, или что-то в этом роде.

Преимущество использования небольшого пространства для использования ссылки на метод лямбда. С помощью лямбда код лямбда скомпилирован в частный статический метод содержащего класса, а затем лямбда-метафайлу вызывается со ссылкой на этот статический метод. В ссылочном примере метода этот метод уже существует в классе java.util.Objects, поэтому лямбда-метафайлер является вызовом со ссылкой на существующий метод. Это приводит к умеренной экономии пространства.

Рассмотрим эти маленькие классы:

class LM { // lambda
    static Predicate<Object> a = x -> x != null;
}

class MR { // method reference
    static Predicate<Object> a = Objects::nonNull;
}

(Заинтересованные читатели должны запустить javap -private -cp classes -c -v <class>, чтобы просмотреть подробные различия между тем, как они скомпилированы.)

В результате получается 1 094 байта для случая лямбда и 989 байтов для случая ссылки метода. (Javac 1.8.0_11.) Это не огромная разница, но если ваши программы, вероятно, будут иметь большое количество лямбда, как это, вы можете подумать о экономии пространства в результате использования ссылок на методы.

Кроме того, более вероятно, что ссылка на метод может быть JIT-скомпилирована и встроена, чем лямбда, так как ссылка на метод, вероятно, используется намного больше. Это может привести к небольшому улучшению производительности. Кажется маловероятным, что это будет иметь практическое значение, однако.

Хотя вы специально сказали "За исключением стиля кода...", это в основном касается стиля. Эти небольшие методы были специально добавлены в API, так что программисты могли использовать имена вместо встроенных lambdas. Это часто улучшает понятность кода. Другим моментом является то, что ссылка на метод часто содержит явную информацию о типе, которая может помочь в случаях сложного типа вывода, таких как вложенные компараторы. (Однако это не относится к Objects::nonNull.) Добавление добавленных или явно введенных параметров лямбда добавляет много беспорядка, поэтому в этих случаях ссылки на методы являются явным выигрышем.