Tengo una pregunta sobre el uso del Function.identity()
método.
Imagine el siguiente código:
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.
¿Hay alguna razón por la que deba usar en Function.identity()
lugar de str->str
(o viceversa). Creo que la segunda opción es más legible (una cuestión de gustos, por supuesto). Pero, ¿hay alguna razón "real" por la que uno debería ser preferido?
java
lambda
java-8
java-stream
Przemysław Głębocki
fuente
fuente
t -> t
simplemente porque es más sucinto.Respuestas:
A partir de la implementación actual de JRE,
Function.identity()
siempre devolverá la misma instancia, mientras que cada apariciónidentifier -> identifier
no solo creará su propia instancia, sino que incluso tendrá una clase de implementación distinta. Para más detalles, ver aquí .La razón es que el compilador genera un método sintético que contiene el cuerpo trivial de esa expresión lambda (en el caso de
x->x
, equivalente areturn identifier;
) y le dice al tiempo de ejecución que cree una implementación de la interfaz funcional que llama a este método. Por lo tanto, el tiempo de ejecución solo ve diferentes métodos de destino y la implementación actual no analiza los métodos para averiguar si ciertos métodos son equivalentes.Por lo tanto, usar en
Function.identity()
lugar dex -> x
podría ahorrar algo de memoria, pero eso no debería impulsar su decisión si realmente cree quex -> x
es más legible queFunction.identity()
.También puede considerar que al compilar con la información de depuración habilitada, el método sintético tendrá un atributo de depuración de línea que apunta a la (s) línea (s) del código fuente que contiene la expresión lambda, por lo tanto, tiene la posibilidad de encontrar el origen de una
Function
instancia particular mientras se depura . Por el contrario, al encontrar la instancia devuelta porFunction.identity()
durante la depuración de una operación, no sabrá quién llamó a ese método y pasó la instancia a la operación.fuente
x -> x
marco. ¿Sugiere establecer el punto de interrupción para esta lambda? Por lo general, no es tan fácil poner el punto de interrupción en el lambda de expresión única (al menos en Eclipse) ...Function.identity()
esa información se pierde. Entonces, la cadena de llamadas puede ayudar en casos simples, pero piense, por ejemplo, en una evaluación de subprocesos múltiples donde el iniciador original no está en el seguimiento de la pila ...new
.new Foo(…)
garantías para crear una nueva instancia del tipo exactoFoo
, mientras que,Foo.getInstance(…)
puede devolver una instancia existente de (un subtipo de)Foo
...En su ejemplo, no hay una gran diferencia entre
str -> str
yFunction.identity()
dado que internamente es simplet->t
.Pero a veces no podemos usar
Function.identity
porque no podemos usar aFunction
. Echa un vistazo aquí:esto compilará bien
pero si intentas compilar
obtendrá un error de compilación como se
mapToInt
esperaToIntFunction
, que no está relacionado conFunction
. AdemásToIntFunction
no tieneidentity()
método.fuente
i -> i
conFunction.identity()
dará lugar a un error de compilación.mapToInt(Integer::intValue)
.mapToInt(i -> i)
es una simplificaciónmapToInt( (Integer i) -> i.intValue())
. Usa la versión que creas que es más clara, para mímapToInt(i -> i)
muestra mejor las intenciones de este código.i -> i
parece una función de identidad, que no es en este caso.i -> i
ya que mi objetivo es asignar Integer a int (lo quemapToInt
sugiere bastante bien) no llamar explícitamente alintValue()
método. Cómo se logrará este mapeo no es realmente tan importante. Así que aceptemos estar en desacuerdo, pero gracias por señalar la posible diferencia de rendimiento, algún día tendré que analizarlo más de cerca.De la fuente JDK :
Entonces, no, siempre que sea sintácticamente correcto.
fuente
t->t
en el código fuente puede crear un objeto y la implementación deFunction.identity()
es una ocurrencia. Por lo tanto, todos los sitios de llamadas que invoquenidentity()
compartirán ese objeto, mientras que todos los sitios que usan explícitamente la expresión lambdat->t
crearán su propio objeto. El métodoFunction.identity()
no es especial de ninguna manera, siempre que cree un método de fábrica que encapsule una expresión lambda de uso común y llame a ese método en lugar de repetir la expresión lambda, puede ahorrar algo de memoria, dada la implementación actual .t->t
objeto cada vez que se llama al método y recicla el mismo cada vez que se llama al método.invokedynamic
instrucción que se vincula en su primera ejecución ejecutando el llamado método bootstrap, que en el caso de las expresiones lambda se encuentra en elLambdaMetafactory
. Esta implementación decide devolver un identificador a un constructor, un método de fábrica o un código que siempre devuelve el mismo objeto. También puede decidir devolver un enlace a un identificador ya existente (que actualmente no sucede).