У меня есть вопрос, касающийся использования Function.identity()
метода.
Представьте себе следующий код:
Arrays.asList("a", "b", "c")
.stream()
.map(Function.identity()) // <- This,
.map(str -> str) // <- is the same as this.
.collect(Collectors.toMap(
Function.identity(), // <-- And this,
str -> str)); // <-- is the same as this.
Есть ли причина, по которой вы должны использовать Function.identity()
вместо str->str
(или наоборот). Я думаю, что второй вариант более читабелен (вопрос вкуса, конечно). Но есть ли «реальная» причина, по которой следует отдавать предпочтение?
java
lambda
java-8
java-stream
Przemysław Głębocki
источник
источник
t -> t
просто, потому что это более кратко.Ответы:
Начиная с текущей реализации JRE,
Function.identity()
всегда будет возвращаться один и тот же экземпляр, в то время как каждое вхождениеidentifier -> identifier
не только создаст свой собственный экземпляр, но даже будет иметь отдельный класс реализации. Для более подробной информации смотрите здесь .Причина в том, что компилятор генерирует синтетический метод, содержащий тривиальное тело этого лямбда-выражения (в случае
x->x
эквивалентногоreturn identifier;
), и указывает среде выполнения создать реализацию функционального интерфейса, вызывающего этот метод. Таким образом, среда выполнения видит только разные целевые методы, а текущая реализация не анализирует методы, чтобы выяснить, эквивалентны ли определенные методы.Поэтому использование
Function.identity()
вместоx -> x
может сэкономить некоторую память, но это не должно влиять на ваше решение, если вы действительно считаете, чтоx -> x
это более читабельно, чемFunction.identity()
.Вы также можете учесть, что при компиляции с включенной отладочной информацией синтетический метод будет иметь атрибут отладки строки, указывающий на строку (и) исходного кода, содержащую лямбда-выражение, поэтому у вас есть шанс найти источник конкретного
Function
экземпляра во время отладки. , Напротив, при обнаружении экземпляра, возвращенногоFunction.identity()
во время отладки операции, вы не будете знать, кто вызвал этот метод и передал экземпляр в операцию.источник
x -> x
фрейм. Вы предлагаете установить точку останова для этой лямбды? Обычно не так просто поместить точку останова в лямбду с одним выражением (по крайней мере, в Eclipse) ...Function.identity()
эта информация теряется. Затем цепочка вызовов может помочь в простых случаях, но подумайте, например, о многопоточной оценке, когда первоначальный инициатор не находится в трассировке стека…new
.new Foo(…)
гарантирует создание нового экземпляра точного типаFoo
, тогда какFoo.getInstance(…)
может вернуть существующий экземпляр (подтип)Foo
…В вашем примере нет большой разницы между
str -> str
иFunction.identity()
так как внутренне это простоt->t
.Но иногда мы не можем использовать,
Function.identity
потому что мы не можем использоватьFunction
. Посмотрите здесь:это скомпилирует нормально
но если вы попытаетесь скомпилировать
вы получите ошибку компиляции, так как
mapToInt
ожидаетToIntFunction
, что не связано сFunction
. ТакжеToIntFunction
нетidentity()
метода.источник
i -> i
наFunction.identity()
приведет к ошибке компилятора.mapToInt(Integer::intValue)
.mapToInt(i -> i)
это упрощениеmapToInt( (Integer i) -> i.intValue())
. Используйте любую версию, которую вы считаете более понятной, для меняmapToInt(i -> i)
лучше показывает намерения этого кода.i -> i
выглядит как функция тождества, чего нет в данном случае.i -> i
так как моя цель состоит в том, чтобы отобразить Integer в int (чтоmapToInt
довольно неплохо), чтобы не вызыватьintValue()
метод явно . Как будет достигнуто это отображение, не так уж важно. Так что давайте просто согласимся не соглашаться, но спасибо за указание на возможную разницу в производительности, мне нужно будет поближе взглянуть на это когда-нибудь.Из источника JDK :
Так что нет, пока это синтаксически правильно.
источник
t->t
в исходном коде может создать один объект и реализациюFunction.identity()
- одно вхождение. Таким образом, все вызывающие сайты вызововidentity()
будут совместно использовать этот один объект, в то время как все сайты, явно использующие лямбда-выражениеt->t
, создадут свой собственный объект. Этот методFunction.identity()
ни в коем случае не является специальным, когда вы создаете фабричный метод, инкапсулирующий обычно используемое лямбда-выражение, и вызываете этот метод вместо повторения лямбда-выражения, вы можете сэкономить некоторую память, учитывая текущую реализацию .t->t
объекта при каждом вызове метода и перезагружает один и тот же объект при каждом вызове метода?invokedynamic
инструкцию, которая связывается при первом выполнении с помощью так называемого метода начальной загрузки, который в случае лямбда-выражений находится вLambdaMetafactory
. Эта реализация решает вернуть дескриптор в конструктор, фабричный метод или код, всегда возвращающий один и тот же объект. Также может быть принято решение вернуть ссылку на уже существующий дескриптор (чего в настоящее время не происходит).