В Java почему Function.identity () является методом stati c вместо чего-то еще? - PullRequest
1 голос
/ 18 января 2020

Java 8 добавлены функциональные программные конструкции, включая класс Function и связанный с ним метод identity().

Вот текущая структура этого метода:

// Current implementation of this function in the [JDK source][1]
static <T> Function<T, T> identity() {
    return t -> t;
}

// Can be used like this
List<T> sameList = list.stream().map(Function.identity()).collect(Collectors.toList());

Однако есть второй способ структурирования:

// Alternative implementation of the method
static <T> T identity(T in) {
    return in;
}

// Can be used like this
List<T> sameList = list.stream().map(Function::identity).collect(Collectors.toList());

Существует даже третий способ структурирования:

// Third implementation
static final Function<T, T> IDENTITY_FUNCTION = t -> t;

// Can be used like this
List<T> sameList = list.stream().map(Function.IDENTITY_FUNCTION).collect(Collectors.toList());

Из трех подходов первый действительно используется в JDK выглядит менее эффективным с точки зрения памяти, поскольку создается впечатление, что он создает новый объект (лямбду) при каждом использовании, в то время как вторая и третья реализации этого не делают. Согласно этому SO-ответу это на самом деле не так, поэтому в конечном итоге все три подхода кажутся относительно эквивалентными с точки зрения производительности.

Использование второго подхода позволяет использовать метод в качестве ссылки на метод, что аналогично тому, как много других стандартных библиотечных методов используются в функциональных конструкциях. Например, stream.map(Math::abs) или stream.map(String::toLowerCase).

В целом, зачем использовать первый подход, который выглядит (хотя, в конечном счете, нет) менее производительным и отличается от других примеров?

1 Ответ

9 голосов
/ 18 января 2020

TL; DR Использование Function.identity() создает только один объект, поэтому он очень эффективно использует память.


Третья реализация не компилируется, поскольку T не определено, поэтому это не вариант.

Во второй реализации каждый раз, когда вы пишете Function::identity a , создается новый экземпляр объекта.

Сначала реализация, всякий раз, когда вы вызываете Function.identity(), возвращается экземпляр к тому же лямбда-объекту *1019*.

Это легко увидеть самим. Начните с создания двух identity методов в одном и том же классе, поэтому переименуйте их в identity1 и identity2, чтобы их можно было отдельно идентифицировать.

static <T> Function<T, T> identity1() {
    return t -> t;
}

static <T> T identity2(T in) {
    return in;
}

Напишите метод test, который принимает Function и печатает объект, чтобы мы могли видеть его уникальную идентичность, что отражено кодом ha sh.

static <A, B> void test(Function<A, B> func) {
    System.out.println(func);
}

Несколько раз вызывать метод test, чтобы увидеть, получает ли каждый новый объект экземпляр или нет (мой код находится в классе с именем Test) .

test(Test.identity1());
test(Test.identity1());
test(Test.identity1());
test(Test::identity2);
test(Test::identity2);
for (int i = 0; i < 3; i++)
    test(Test::identity2);

Выход

Test$$Lambda$1/0x0000000800ba0840@7adf9f5f
Test$$Lambda$1/0x0000000800ba0840@7adf9f5f
Test$$Lambda$1/0x0000000800ba0840@7adf9f5f
Test$$Lambda$2/0x0000000800ba1040@5674cd4d
Test$$Lambda$3/0x0000000800ba1440@65b54208
Test$$Lambda$4/0x0000000800ba1840@6b884d57
Test$$Lambda$4/0x0000000800ba1840@6b884d57
Test$$Lambda$4/0x0000000800ba1840@6b884d57

Как вы можете видите, несколько операторов , вызывающих Test.identity1() все получают один и тот же объект, но несколько операторов с использованием Test::identity2 все получают разные объекты.

Это правда, что повторное выполнение одного и того же оператора получает один и тот же объект (как видно из результата l oop), но это отличается от результата, полученного из различных операторов.

Вывод: Использование Test.identity1() создает только один объект, так что это больше память эффективнее, чем при использовании Test::identity2.

...