Por que não podemos usar methods padrão em expressões lambda?

Eu estava lendo este tutorial no Java 8, onde o escritor mostrou o código:

interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } 

E então disse

Métodos padrão não podem ser acessados ​​de dentro de expressões lambda. O código a seguir não compila:

 Formula formula = (a) -> sqrt( a * 100); 

Mas ele não explicou por que isso não é possível. Eu corri o código, e deu um erro,

tipos incompatíveis: a fórmula não é uma interface funcional`

Então, por que não é possível ou qual é o significado do erro? A interface atende ao requisito de uma interface funcional com um método abstrato.

É mais ou menos uma questão de escopo. Do JLS

Ao contrário do código que aparece nas declarações de class anônima, o significado de nomes e as super palavras this e super aparecendo em um corpo lambda, juntamente com a acessibilidade das declarações referenciadas, são os mesmos do contexto circundante (exceto que os parâmetros lambda introduzem novos nomes).

Em seu exemplo tentado

 Formula formula = (a) -> sqrt( a * 100); 

o escopo não contém uma declaração para o nome sqrt .

Isso também é sugerido no JLS

Praticamente falando, é incomum que uma expressão lambda precise falar sobre si mesma (seja para chamar-se recursivamente ou invocar seus outros methods), enquanto é mais comum querer usar nomes para se referir a coisas da class que o incluam. caso contrário, ser sombreado ( this , toString() ). Se for necessário que uma expressão lambda se refira a si mesma (como se fosse por meio this ), uma referência de método ou uma class interna anônima deve ser usada.

Eu acho que poderia ter sido implementado. Eles escolheram não permitir isso.

As expressões lambda funcionam de uma maneira completamente diferente das classs anônimas, pois this representa a mesma coisa que faria no escopo em torno da expressão.

Por exemplo, isso compila

 class Main { public static void main(String[] args) { new Main().foo(); } void foo() { System.out.println(this); Runnable r = () -> { System.out.println(this); }; r.run(); } } 

e imprime algo como

 Main@f6f4d33 Main@f6f4d33 

Em outras palavras, this é um Main , em vez do object criado pela expressão lambda.

Portanto, você não pode usar sqrt em sua expressão lambda porque o tipo this referência não é Formula ou um subtipo e não possui um método sqrt .

Formula é uma interface funcional, e o código

 Formula f = a -> a; 

compila e corre para mim sem nenhum problema.

Embora você não possa usar uma expressão lambda para isso, você pode fazê-lo usando uma class anônima, como esta:

 Formula f = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; 

Isso não é exatamente verdade. Métodos padrão podem ser usados ​​em expressões lambda.

 interface Value { int get(); default int getDouble() { return get() * 2; } } public static void main(String[] args) { List list = Arrays.asList( () -> 1, () -> 2 ); int maxDoubled = list.stream() .mapToInt(val -> val.getDouble()) .max() .orElse(0); System.out.println(maxDoubled); } 

imprime 4 conforme o esperado e usa um método padrão dentro de uma expressão lambda ( .mapToInt(val -> val.getDouble()) )

O que o autor do seu artigo tenta fazer aqui

 Formula formula = (a) -> sqrt( a * 100); 

é definir uma Formula , que funciona como interface funcional, diretamente através de uma expressão lambda.

Isso funciona bem, no código de exemplo acima, Value value = () -> 5 ou com a Formula como interface por exemplo

 Formula formula = (a) -> 2 * a * a + 1; 

Mas

 Formula formula = (a) -> sqrt( a * 100); 

falha porque está tentando acessar o método ( sqrt ), mas não consegue. Os Lambdas, conforme especificado, herdam seu escopo de seus arredores, o que significa que this dentro de um lambda se refere à mesma coisa que diretamente fora dele. E não há método de sqrt fora.

Minha explicação pessoal para isso: Dentro da expressão lambda, não está claro para qual interface funcional concreta o lambda será “convertido”. Comparar

 interface NotRunnable { void notRun(); } private final Runnable r = () -> { System.out.println("Hello"); }; private final NotRunnable r2 = r::run; 

A mesma expressão lambda pode ser “convertida” em vários tipos. Eu penso nisso como se um lambda não tivesse um tipo. É uma function sem tipo especial que pode ser usada para qualquer interface com os parâmetros certos. Mas essa restrição significa que você não pode usar methods do tipo futuro porque não pode saber.

Isso acrescenta pouco à discussão, mas eu achei interessante de qualquer maneira.

Outra maneira de ver o problema seria pensar sobre isso do ponto de vista de um lambda auto-referenciado.

Por exemplo:

 Formula formula = (a) -> formula.sqrt(a * 100); 

Parece que isso deve fazer sentido, já que no momento em que o lambda é executado, a referência de formula já deve ter sido inicializada (ou seja, não há maneira de fazer formula.apply() até que a formula tenha sido inicializada corretamente. Caso, do corpo do lambda, o corpo de apply , deve ser possível fazer referência a mesma variável).

No entanto isso também não funciona. Curiosamente, costumava ser possível no começo. Você pode ver que Maurice Naftalin documentou isso em seu site de FAQ da Lambda . Mas, por algum motivo, o suporte para esse recurso foi removido .

Algumas das sugestões dadas em outras respostas a esta pergunta já foram mencionadas lá na própria discussão na lista de discussão lambda.

Os methods padrão só podem ser acessados ​​com referências de object, se você quiser acessar o método padrão, você terá uma referência de object da Interface Funcional, no corpo do método de expressão lambda que você não terá, portanto, não poderá acessá-lo.

Você está recebendo um incompatible types: Formula is not a functional interface erros incompatible types: Formula is not a functional interface porque você não forneceu a anotação @FunctionalInterface ; se você forneceu o erro ‘método undefined’, o compilador forçará você a criar um método na class.

@FunctionalInterface deve ter apenas um método abstrato que sua Interface tenha, mas está faltando a anotação.

Mas os methods estáticos não têm essa restrição, já que podemos acessá-lo sem referência a objects, como abaixo.

 @FunctionalInterface public interface Formula { double calculate(int a); static double sqrt(int a) { return Math.sqrt(a); } } public class Lambda { public static void main(String[] args) { Formula formula = (a) -> Formula.sqrt(a); System.out.println(formula.calculate(100)); } } 
Intereting Posts