Nota: esta pergunta se origina de um link morto que era uma pergunta anterior do SO, mas aqui vai ...
Veja este código ( nota: eu sei que esse código não "funcionará" e que Integer::compare
deve ser usado - eu apenas o extraí da pergunta 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());
De acordo com o javadoc de .min()
e .max()
, o argumento de ambos deve ser a Comparator
. No entanto, aqui as referências de método são para métodos estáticos da Integer
classe.
Então, por que isso é compilado?
java
java-8
java-stream
fge
fonte
fonte
Integer::compare
vez deInteger::max
eInteger::min
.Integer
não são métodos deComparator
.Respostas:
Deixe-me explicar o que está acontecendo aqui, porque não é óbvio!
Primeiro,
Stream.max()
aceita uma instância deComparator
para que os itens no fluxo possam ser comparados entre si para encontrar o mínimo ou o máximo, em uma ordem ideal com a qual você não precisa se preocupar muito.Portanto, a questão é, obviamente, por que é
Integer::max
aceito? Afinal, não é um comparador!A resposta está na maneira como a nova funcionalidade lambda funciona no Java 8. Ela se baseia em um conceito que é conhecido informalmente como interfaces de "método abstrato único" ou interfaces "SAM". A idéia é que qualquer interface com um método abstrato possa ser implementada automaticamente por qualquer lambda - ou referência de método - cuja assinatura de método corresponda ao método único na interface. Então, examinando a
Comparator
interface (versão simples):Se um método está procurando por um
Comparator<Integer>
, então está basicamente procurando por esta assinatura:Eu uso "xxx" porque o nome do método não é usado para fins de correspondência .
Portanto, ambos
Integer.min(int a, int b)
eInteger.max(int a, int b)
estão próximos o suficiente para que a caixa automática permita que isso apareça comoComparator<Integer>
em um contexto de método.fonte
list.stream().mapToInt(i -> i).max().get()
..getAsInt()
vez deget()
, como você está lidando com umOptionalInt
.max()
função!Comparator
documentação, podemos ver que ela é decorada com a anotação@FunctionalInterface
. Este decorador é a mágica que permiteInteger::max
eInteger::min
pode ser convertida em aComparator
.@FunctionalInterface
é principalmente para fins de documentação, pois o compilador pode fazer isso felizmente com qualquer interface com um único método abstrato.Comparator
é uma interface funcional eInteger::max
está em conformidade com essa interface (depois de considerar o autoboxing / unboxing). Ele pega doisint
valores e retorna umint
- exatamente como você esperariaComparator<Integer>
(novamente, apertando os olhos para ignorar a diferença Inteiro / int).No entanto, eu não esperaria que ele fizesse a coisa certa, já que
Integer.max
isso não está de acordo com a semântica deComparator.compare
. E, de fato, realmente não funciona em geral. Por exemplo, faça uma pequena alteração:... e agora o
max
valor é -20 e omin
valor é -1.Em vez disso, as duas chamadas devem usar
Integer::compare
:fonte
Comparator<Integer>
teriaint compare(Integer, Integer)
... não é incompreensível que o Java permite uma referência método deint max(int, int)
converter a isso ...Integer::max
? Da perspectiva dele, você passou por uma função que atendeu às suas especificações, é tudo o que pode realmente continuar.Comparator.compare
. Deve retornar umenum
de{LessThan, GreaterThan, Equal}
, não umint
. Dessa forma, a interface funcional não corresponderia e você obteria um erro de compilação. IOW: a assinatura de tipo deComparator.compare
não captura adequadamente a semântica do que significa comparar dois objetos e, portanto, outras interfaces que não têm absolutamente nada a ver com a comparação de objetos acidentalmente têm a mesma assinatura de tipo.Isso funciona porque
Integer::min
resolve uma implementação daComparator<Integer>
interface.A referência do método
Integer::min
resolveInteger.min(int a, int b)
, resolve eIntBinaryOperator
, presumivelmente, a autobox ocorre em algum lugar, tornando-o aBinaryOperator<Integer>
.E os
min()
respmax()
métodos daStream<Integer>
pedir aComparator<Integer>
interface a ser implementado.Agora isso resolve para o método único
Integer compareTo(Integer o1, Integer o2)
. Qual é do tipoBinaryOperator<Integer>
.E assim a mágica aconteceu porque os dois métodos são a
BinaryOperator<Integer>
.fonte
Integer::min
implementaComparable
. Não é um tipo que pode implementar qualquer coisa. Mas é avaliado em um objeto que implementaComparable
.Comparator<Integer>
é uma interface de método único abstrato (também conhecida como "funcional") eInteger::min
cumpre seu contrato, portanto o lambda pode ser interpretado como este. Não sei como você vê o BinaryOperator entrando em jogo aqui (ou IntBinaryOperator, também) - não há relação de subtipagem entre isso e o Comparator.Além das informações fornecidas por David M. Lloyd, pode-se acrescentar que o mecanismo que permite isso é chamado de digitação de destino .
A idéia é que o tipo que o compilador atribui a expressões lambda ou referências a métodos não depende apenas da expressão em si, mas também de onde é usado.
O destino de uma expressão é a variável à qual seu resultado é atribuído ou o parâmetro ao qual seu resultado é passado.
As expressões lambda e as referências de método recebem um tipo que corresponde ao tipo de seu destino, se esse tipo puder ser encontrado.
Consulte a seção Inferência de tipo no Tutorial Java para obter mais informações.
fonte
Eu tive um erro com uma matriz obtendo o máximo e o mínimo, então minha solução foi:
fonte