una - funciones anonimas java
¿Por qué no podemos usar métodos por defecto en expresiones lambda? (5)
Es más o menos una cuestión de alcance. De la JLS
A diferencia del código que aparece en las declaraciones de clase anónimas, el significado de los nombres y las palabras clave
this
ysuper
aparecen en un cuerpo lambda, junto con la accesibilidad de las declaraciones referenciadas, son las mismas que en el contexto circundante (excepto que los parámetros lambda introducen nombres nuevos).
En tu intento de ejemplo
Formula formula = (a) -> sqrt( a * 100);
el alcance no contiene una declaración para el nombre sqrt
.
Esto también se insinúa en el JLS
Hablando en términos prácticos, es inusual que una expresión lambda necesite hablar sobre sí misma (ya sea para llamarse recursivamente o para invocar sus otros métodos), mientras que es más común querer usar nombres para referirse a cosas en la clase adjunta que lo haría de lo contrario, se sombreará (
this
,toString()
). Si es necesario que una expresión lambda se refiera a sí misma (como a través dethis
), en su lugar se debe usar una referencia de método o una clase interna anónima.
Creo que podría haberse implementado. Eligieron no permitirlo.
Estaba leyendo este tutorial en Java 8 donde el escritor mostró el código:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Y luego dijo
No se puede acceder a los métodos predeterminados desde expresiones lambda. El siguiente código no se compila:
Formula formula = (a) -> sqrt( a * 100);
Pero él no explicó por qué no es posible. Ejecuté el código, y me dio un error,
tipos incompatibles: Fórmula no es una interfaz funcional`
Entonces, ¿por qué no es posible o cuál es el significado del error? La interfaz cumple el requisito de una interfaz funcional que tiene un método abstracto.
Eso no es exactamente cierto. Los métodos predeterminados se pueden usar en expresiones lambda.
interface Value {
int get();
default int getDouble() {
return get() * 2;
}
}
public static void main(String[] args) {
List<Value> list = Arrays.asList(
() -> 1,
() -> 2
);
int maxDoubled = list.stream()
.mapToInt(val -> val.getDouble())
.max()
.orElse(0);
System.out.println(maxDoubled);
}
imprime 4
como se esperaba y usa un método predeterminado dentro de una expresión lambda ( .mapToInt(val -> val.getDouble())
)
Lo que el autor de tu artículo intenta hacer aquí
Formula formula = (a) -> sqrt( a * 100);
es definir una Formula
, que funciona como interfaz funcional, directamente a través de una expresión lambda.
Eso funciona bien, en el código de ejemplo anterior, Value value = () -> 5
o con Formula
como interfaz por ejemplo
Formula formula = (a) -> 2 * a * a + 1;
Pero
Formula formula = (a) -> sqrt( a * 100);
falla porque está intentando acceder al método sqrt
( this.
) pero no puede. Las lambdas, por especificación, heredan su alcance de su entorno, lo que significa que this
dentro de una lambda se refiere a lo mismo que directamente fuera de ella. Y no hay un método sqrt
afuera.
Mi explicación personal para esto: dentro de la expresión lambda, no está muy claro a qué interfaz funcional concreta se va a "convertir" la lambda. Comparar
interface NotRunnable {
void notRun();
}
private final Runnable r = () -> {
System.out.println("Hello");
};
private final NotRunnable r2 = r::run;
La misma expresión lambda se puede "lanzar" a varios tipos. Lo pienso como si un lambda no tuviera un tipo. Es una función especial sin tipo que se puede usar para cualquier interfaz con los parámetros correctos. Pero esa restricción significa que no puede usar métodos del tipo futuro porque no puede conocerlo.
Esto agrega poco a la discusión, pero de todos modos me pareció interesante.
Otra forma de ver el problema sería pensarlo desde el punto de vista de una lambda autorreferencial.
Por ejemplo:
Formula formula = (a) -> formula.sqrt(a * 100);
Parecería que esto debería tener sentido, ya que para cuando se ejecuta la lambda, la referencia de la formula
ya debe haberse inicializado (es decir, no hay forma de hacer formula.apply()
hasta que la formula
se haya inicializado correctamente, en cuyo caso, del cuerpo de la lambda, el cuerpo de apply
, debería ser posible hacer referencia a la misma variable).
Sin embargo, esto tampoco funciona. Curiosamente, solía ser posible al principio. Puede ver que Maurice Naftalin lo había documentado en su sitio web de Lambda FAQ . Pero, por alguna razón, el soporte para esta función finalmente se eliminó .
Algunas de las sugerencias dadas en otras respuestas a esta pregunta ya han sido mencionadas allí en la misma discusión en la lista de correo de lambda.
Las expresiones Lambda funcionan de una manera completamente diferente a las clases anónimas en que this
representa lo mismo que en el ámbito que rodea la expresión.
Por ejemplo, esto 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 así como
Main@f6f4d33
Main@f6f4d33
En otras palabras, this
es un Main
, en lugar del objeto creado por la expresión lambda.
Por lo tanto, no puede usar sqrt
en su expresión lambda porque el tipo de this
referencia no es Formula
, o un subtipo, y no tiene un método sqrt
.
Formula
es una interfaz funcional y el código
Formula f = a -> a;
compila y corre para mí sin ningún problema.
Aunque no puede usar una expresión lambda para esto, puede hacerlo utilizando una clase anónima, como esta:
Formula f = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
Se puede acceder a los métodos predeterminados solo con referencias a objetos, si quieres acceder al método predeterminado, tendrás una referencia de objeto de la Interfaz funcional, en el cuerpo del método de expresión lambda no lo tendrás, así que no puedes acceder a él.
@FunctionalInterface
un error incompatible types: Formula is not a functional interface
porque no ha proporcionado la anotación @FunctionalInterface
, si ha proporcionado obtendrá el error ''método indefinido'', el compilador lo obligará a crear un método en la clase.
@FunctionalInterface
debe tener solo un método abstracto que su Interfaz tenga, pero le falta la anotación.
Pero los métodos estáticos no tienen tal restricción, ya que podemos acceder a ella sin referencia a los objetos como a continuación.
@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));
}
}