Existe uma maneira de comparar lambdas?

Digamos que eu tenha uma lista de objects que foram definidos usando expressões lambda (closures). Existe uma maneira de inspecioná-los para que possam ser comparados?

O código em que estou mais interessado é

List strategies = getStrategies(); Strategy a = (Strategy) this::a; if (strategies.contains(a)) { // ... 

O código completo é

 import java.util.Arrays; import java.util.List; public class ClosureEqualsMain { interface Strategy { void invoke(/*args*/); default boolean equals(Object o) { // doesn't compile return Closures.equals(this, o); } } public void a() { } public void b() { } public void c() { } public List getStrategies() { return Arrays.asList(this::a, this::b, this::c); } private void testStrategies() { List strategies = getStrategies(); System.out.println(strategies); Strategy a = (Strategy) this::a; // prints false System.out.println("strategies.contains(this::a) is " + strategies.contains(a)); } public static void main(String... ignored) { new ClosureEqualsMain().testStrategies(); } enum Closures {; public static  boolean equals(Closure c1, Closure c2) { // This doesn't compare the contents // like others immutables eg String return c1.equals(c2); } public static  int hashCode(Closure c) { return // a hashCode which can detect duplicates for a Set } public static  String asString(Closure c) { return // something better than Object.toString(); } } public String toString() { return "my-ClosureEqualsMain"; } } 

Parece que a única solução é definir cada lambda como um campo e usar apenas esses campos. Se você quiser imprimir o método chamado, é melhor usar o Method . Existe uma maneira melhor com expressões lambda?

Além disso, é possível imprimir um lambda e obter algo legível? Se você imprimir this::a vez de

 ClosureEqualsMain$$Lambda$1/821270929@3f99bd52 

obter algo como

 ClosureEqualsMain.a() 

ou até mesmo usar this.toString e o método.

 my-ClosureEqualsMain.a(); 

Essa questão poderia ser interpretada em relação à especificação ou à implementação. Obviamente, as implementações podem mudar, mas você pode estar disposto a rewrite seu código quando isso acontecer, então vou responder em ambos.

Também depende do que você quer fazer. Você está olhando para otimizar, ou você está procurando ironclad garante que duas instâncias são (ou não) a mesma function? (Se o último, você vai encontrar-se em desacordo com a física computacional, em que mesmo problemas tão simples como perguntar se duas funções computam a mesma coisa são indecidíveis.)

De uma perspectiva de especificação, a especificação de linguagem promete apenas que o resultado de avaliar (não invocar) uma expressão lambda é uma instância de uma class implementando a interface funcional de destino. Não faz promises sobre a identidade ou o grau de aliasing do resultado. Isso é por design, para dar flexibilidade máxima de implementações para oferecer melhor desempenho (isto é como lambdas pode ser mais rápido que as classs internas; não estamos vinculados à restrição “deve criar instância exclusiva” que classs internas são.)

Então, basicamente, a especificação não lhe dá muito, exceto obviamente que dois lambdas que são de referência igual (= =) vão computar a mesma function.

De uma perspectiva de implementação, você pode concluir um pouco mais. Há (atualmente, pode alterar) um relacionamento 1: 1 entre as classs sintéticas que implementam lambdas e os locais de captura no programa. Portanto, dois pedaços separados de código que capturam “x -> x + 1” podem ser mapeados para classs diferentes. Mas se você avaliar o mesmo lambda no mesmo site de captura e o lambda não estiver capturando, obterá a mesma instância, que pode ser comparada com a igualdade de referência.

Se seus lambdas são serializáveis, eles vão desistir de seu estado mais facilmente, em troca de sacrificar algum desempenho e segurança (sem almoço grátis).

Uma área em que pode ser prático ajustar a definição de igualdade é com referências de método, porque isso permitiria que elas fossem usadas como ouvintes e fossem devidamente não registradas. Isso está sob consideração.

Eu acho que o que você está tentando chegar é: se dois lambdas são convertidos para a mesma interface funcional, são representados pela mesma function de comportamento e possuem argumentos capturados idênticos, eles são os mesmos

Infelizmente, isso é difícil de fazer (para lambdas não serializáveis, você não pode obter todos os componentes disso) e não o suficiente (porque dois arquivos compilados separadamente podem converter o mesmo lambda para o mesmo tipo de interface funcional, e você pode ser capaz de dizer.)

O GE discutiu se deveria expor informações suficientes para poder fazer esses julgamentos, bem como discutir se os lambdas deveriam implementar mais seletivos equals / hashCode ou mais descritivos toString. A conclusão foi que não estávamos dispostos a pagar nada pelo custo de desempenho para disponibilizar essa informação ao chamador (troca ruim, punindo 99,99% dos usuários por algo que beneficia 0,01%).

Uma conclusão definitiva sobre toString não foi alcançada, mas deixada em aberto para ser revisitada no futuro. No entanto, houve alguns bons argumentos feitos em ambos os lados sobre esta questão; isso não é um afundanço.

Eu não vejo a possibilidade de obter essas informações do fechamento em si. Os fechamentos não fornecem estado.

Mas você pode usar o Java-Reflection, se quiser inspecionar e comparar os methods. Claro que isso não é uma solução muito bonita, por causa do desempenho e das exceções, que são para pegar. Mas desta forma você obtém essas meta-informações.

Para comparar labmdas, costumo deixar a interface estender Serializable e, em seguida, comparar os bytes serializados. Não é muito bom, mas funciona para a maioria dos casos.