¿Por qué no podemos usar métodos predeterminados en expresiones lambda?


Estaba leyendo este tutorial sobre 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 el siguiente código no compila:

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

Pero no explicó por qué no es posible. Corrí el código, y dio un error,

Tipos incompatibles: La fórmula no es una interfaz funcional '

Entonces, ¿por qué no es posible o qué es el significado del error? La interfaz cumple con el requisito de una interfaz funcional que tenga un método abstracto.

Author: Peter Mortensen, 2015-10-13

5 answers

Es más o menos una cuestión de alcance. De la JLS

A diferencia del código que aparece en declaraciones de clase anónimas, el significado de nombres y las palabras clave this y super que aparecen en un cuerpo lambda, junto con la accesibilidad de las declaraciones referenciadas, son los mismos como en el contexto circundante (excepto que los parámetros lambda introducen nuevos nombres).

En su ejemplo intentado

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

El ámbito de aplicación no contiene una declaración para el nombre sqrt.

Esto también se insinúa en la JLS

En términos prácticos, es inusual que una expresión lambda necesite hablar de sí mismo (ya sea para llamarse recursivamente o para invocar su otros métodos), mientras que es más común querer usar nombres para referirse a cosas en la clase que encierra que de otra manera serían sombreadas (this, toString()). Si es necesario que una expresión lambda se refiere a sí mismo (como si fuera a través de this), un método referencia o anónimo la clase interna debe ser usada en su lugar.

Creo que podría haberse implementado. Optaron por no permitirlo.

 23
Author: Sotirios Delimanolis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 17:37:59

Las expresiones Lambda funcionan de una manera completamente diferente a las clases anónimas en que this representa lo mismo que lo haría en el ámbito que rodea a 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();
    }
}

Y imprime algo como

Main@f6f4d33
Main@f6f4d33

En otras palabras this es un Main, en lugar del objeto creado por la expresión lambda.

Así que no puede usar sqrt en su expresión lambda porque el tipo de referencia this no es Formula, o un subtipo, y no tiene un método sqrt.

Formula es una interfaz funcional, sin embargo, y el código

Formula f = a -> a;

Compila y ejecuta para mí sin ningún problema.

Aunque no puede usar una expresión lambda para esto, puede hacerlo usando una clase anónima, como esta:

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};
 12
Author: Paul Boddington,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 18:31:09

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 espera y utiliza un método predeterminado dentro de una expresión lambda (.mapToInt(val -> val.getDouble()))

Lo que el autor de su artículo intenta hacer aquí

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

Es definir un Formula, que funciona como interfaz funcional, directamente a través de una expresión lambda.

Que funciona bien, en el código de ejemplo anterior, Value value = () -> 5 o con Formula como interfaz para ejemplo

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

Pero

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

Falla porque está tratando de acceder a la (this.)sqrt método pero no puede. Las lambdas según las especificaciones 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 sqrt método fuera.

Mi explicación personal para esto: Dentro de la expresión lambda, no está realmente claro 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 "convertir" a varios tipos. Pienso en ello como si una Lambda no tuviera un tipo. Es un especial abreviado función que se puede utilizar para cualquier Interfaz con los parámetros correctos. Pero esa restricción significa que no puedes usar métodos del tipo futuro porque no puedes saberlo.

 6
Author: zapl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-14 08:49:39

Esto añade poco a la discusión, pero me pareció interesante de todos modos.

Otra forma de ver el problema sería pensarlo desde el punto de vista de una lambda autorreferenciante.

Por ejemplo:

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

Parece que esto debería tener sentido, ya que para cuando se ejecute la lambda, la referencia formula ya debe haber sido inicializada (es decir, no hay forma de hacerlo formula.apply() hasta que formula haya sido inicializada correctamente, en cuyo caso, desde el 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 documentó en su Lambda FAQ Web Site . Pero por alguna razón el soporte para esta característica fue finalmente eliminado.

Algunas de las sugerencias dadas en otras respuestas a esta pregunta ya se han mencionado allí en el mismo discusión en la lista de correo lambda.

 1
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-15 05:15:00

Los métodos predeterminados solo se pueden acceder con referencias de objetos, si desea acceder al método predeterminado, tendría una referencia de objeto de Interfaz Funcional, en el cuerpo del método de expresión lambda no tendrá, por lo que no puede acceder a él.

Está obteniendo un error incompatible types: Formula is not a functional interface porque no ha proporcionado una anotación @FunctionalInterface, si ha proporcionado obtendrá un error 'method undefined', el compilador lo obligará a crear un método en la clase.

@FunctionalInterface debe tener solo un método abstracto su La interfaz tiene eso 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 de objeto 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));
    }

}
 -1
Author: Saravana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 17:25:52