Nota: esta pregunta se origina en un enlace inactivo que era una pregunta SO anterior, pero aquí va ...
Vea este código ( nota: sé que este código no "funcionará" y que Integer::compare
debería usarse, lo acabo de extraer de la pregunta vinculada ):
final ArrayList <Integer> list
= IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());
System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());
Según el javadoc de .min()
y .max()
, el argumento de ambos debería ser a Comparator
. Sin embargo, aquí las referencias de métodos son a métodos estáticos de la Integer
clase.
Entonces, ¿por qué esto compila en absoluto?
java
java-8
java-stream
fge
fuente
fuente
Integer::compare
lugar deInteger::max
yInteger::min
.Integer
no son métodos deComparator
.Respuestas:
Déjame explicarte lo que está sucediendo aquí, ¡porque no es obvio!
Primero,
Stream.max()
acepta una instancia deComparator
modo que los elementos en la secuencia se puedan comparar entre sí para encontrar el mínimo o el máximo, en un orden óptimo del que no tenga que preocuparse demasiado.Entonces la pregunta es, por supuesto, ¿por qué se
Integer::max
acepta? ¡Después de todo no es un comparador!La respuesta está en la forma en que la nueva funcionalidad lambda funciona en Java 8. Se basa en un concepto que se conoce informalmente como interfaces de "método abstracto único" o interfaces "SAM". La idea es que cualquier interfaz con un método abstracto puede implementarse automáticamente por cualquier lambda, o referencia de método, cuya firma de método coincida con el método en la interfaz. Entonces, examinando la
Comparator
interfaz (versión simple):Si un método está buscando un
Comparator<Integer>
, entonces esencialmente está buscando esta firma:Yo uso "xxx" porque el nombre del método no se utiliza con fines coincidentes .
Por lo tanto, tanto
Integer.min(int a, int b)
yInteger.max(int a, int b)
son tan cerca que autoboxing permitirá que esto aparezca como unaComparator<Integer>
en un contexto método.fuente
list.stream().mapToInt(i -> i).max().get()
..getAsInt()
lugar deget()
sin embargo, ya que se trata de unOptionalInt
.max()
función!Comparator
documentación podemos ver que está decorada con la anotación@FunctionalInterface
. Este decorador es la magia que permiteInteger::max
yInteger::min
se convierte en aComparator
.@FunctionalInterface
es principalmente para fines de documentación, ya que el compilador puede hacerlo felizmente con cualquier interfaz con un solo método abstracto.Comparator
es una interfaz funcional yInteger::max
cumple con esa interfaz (después de tomar en cuenta el autoboxing / unboxing). Toma dosint
valores y devuelve unint
- tal como esperaría unComparator<Integer>
a (nuevamente, entrecerrando los ojos para ignorar la diferencia Entero / int).Sin embargo, no esperaría que hiciera lo correcto, dado que
Integer.max
no cumple con la semántica deComparator.compare
. Y, de hecho, en realidad no funciona en general. Por ejemplo, haga un pequeño cambio:... y ahora el
max
valor es -20 y elmin
valor es -1.En cambio, ambas llamadas deben usar
Integer::compare
:fuente
Comparator<Integer>
tendríaint compare(Integer, Integer)
... no es alucinante que Java permita una referencia de métodoint max(int, int)
para convertir a eso ...Integer::max
? Desde su perspectiva, usted pasó una función que cumplió con sus especificaciones, eso es todo lo que realmente puede continuar.Comparator.compare
. Debería devolver unenum
de{LessThan, GreaterThan, Equal}
, no unint
. De esa manera, la interfaz funcional no coincidiría realmente y obtendría un error de compilación. IOW: la firma de tipo deComparator.compare
no captura adecuadamente la semántica de lo que significa comparar dos objetos y, por lo tanto, otras interfaces que no tienen absolutamente nada que ver con la comparación de objetos tienen accidentalmente la misma firma de tipo.Esto funciona porque
Integer::min
resuelve una implementación de laComparator<Integer>
interfaz.La referencia de método de
Integer::min
resuelveInteger.min(int a, int b)
, resuelveIntBinaryOperator
y presumiblemente autoboxing ocurre en algún lugar, lo que lo convierte en unBinaryOperator<Integer>
.Y los métodos de
min()
respuesta solicitan la implementación de la interfaz. Ahora esto se resuelve con el método único . Cuál es de tipo .max()
Stream<Integer>
Comparator<Integer>
Integer compareTo(Integer o1, Integer o2)
BinaryOperator<Integer>
Y así, la magia ha sucedido ya que ambos métodos son a
BinaryOperator<Integer>
.fuente
Integer::min
implementaComparable
. No es un tipo que pueda implementar nada. Pero se evalúa en un objeto que implementaComparable
.Comparator<Integer>
es una interfaz de método abstracto único (también conocido como "funcional") yInteger::min
cumple con su contrato, por lo que la lambda se puede interpretar como esto. No sé cómo ves que BinaryOperator entra en juego aquí (o IntBinaryOperator, tampoco): no hay una relación de subtipo entre eso y Comparator.Además de la información proporcionada por David M. Lloyd, se podría agregar que el mecanismo que permite esto se llama tipificación de objetivos .
La idea es que el tipo que el compilador asigna a las expresiones lambda o referencias de un método no depende solo de la expresión en sí, sino también de dónde se usa.
El objetivo de una expresión es la variable a la que se asigna su resultado o el parámetro al que se pasa su resultado.
Las expresiones Lambda y las referencias de métodos tienen asignado un tipo que coincide con el tipo de su destino, si se puede encontrar dicho tipo.
Consulte la sección Inferencia de tipo en el Tutorial de Java para obtener más información.
fuente
Tuve un error con una matriz obteniendo el máximo y el mínimo, por lo que mi solución fue:
fuente