¿Por qué no se permite "final" en los métodos de interfaz de Java 8?

Resuelto Lukas Eder asked hace 10 años • 5 respuestas

Una de las características más útiles de Java 8 son los nuevos defaultmétodos en las interfaces. Básicamente, existen dos razones (puede haber otras) por las que se han introducido:

  • Proporcionar implementaciones predeterminadas reales. Ejemplo:Iterator.remove()
  • Permitiendo la evolución de la API JDK. Ejemplo:Iterable.forEach()

Desde la perspectiva de un diseñador de API, me hubiera gustado poder utilizar otros modificadores en los métodos de interfaz, por ejemplo final. Esto sería útil al agregar métodos convenientes, evitando anulaciones "accidentales" en la implementación de clases:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Lo anterior ya es una práctica común si Senderfuera una clase:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Ahora, defaulty finalson palabras clave obviamente contradictorias, pero la palabra clave default en sí no habría sido estrictamente requerida , así que supongo que esta contradicción es deliberada, para reflejar las diferencias sutiles entre "métodos de clase con cuerpo" (solo métodos) y "interfaz". métodos con cuerpo" (métodos predeterminados), es decir, diferencias que aún no he entendido.

En algún momento, la compatibilidad con modificadores como staticy finalen métodos de interfaz aún no se exploró por completo, citando a Brian Goetz :

La otra parte es hasta dónde vamos a llegar para admitir herramientas de creación de clases en interfaces, como métodos finales, métodos privados, métodos protegidos, métodos estáticos, etc. La respuesta es: aún no lo sabemos.

Desde entonces, a finales de 2011, obviamente, staticse agregó soporte para métodos en interfaces. Claramente, esto añadió mucho valor a las propias bibliotecas JDK, como por ejemplo con Comparator.comparing().

Pregunta:

¿Cuál es la razón final(y también static final) por la que nunca llegó a las interfaces de Java 8?

Lukas Eder avatar May 04 '14 13:05 Lukas Eder
Aceptado

Esta pregunta está, hasta cierto punto, relacionada con ¿Cuál es la razón por la cual no se permite "sincronizado" en los métodos de interfaz de Java 8?

La clave que hay que entender acerca de los métodos predeterminados es que el objetivo principal del diseño es la evolución de la interfaz , no "convertir las interfaces en rasgos (mediocres)". Si bien hay cierta superposición entre los dos, y tratamos de adaptarnos al segundo para que no interfiriera con el primero, estas preguntas se entienden mejor cuando se ven desde esta perspectiva. (Tenga en cuenta también que los métodos de clase serán diferentes de los métodos de interfaz, sin importar cuál sea la intención, en virtud del hecho de que los métodos de interfaz se pueden heredar múltiples veces).

La idea básica de un método predeterminado es: es un método de interfaz con una implementación predeterminada y una clase derivada puede proporcionar una implementación más específica. Y debido a que el centro de diseño era la evolución de la interfaz, era un objetivo de diseño crítico que los métodos predeterminados pudieran agregarse a las interfaces después del hecho de una manera compatible con el código fuente y con los binarios.

La respuesta demasiado simple a "¿por qué no los métodos predeterminados finales?" es que entonces el cuerpo no sería simplemente la implementación predeterminada, sino que sería la única implementación. Si bien es una respuesta demasiado simple, nos da una pista de que la pregunta ya va en una dirección cuestionable.

Otra razón por la que los métodos de interfaz final son cuestionables es que crean problemas imposibles para los implementadores. Por ejemplo, supongamos que tiene:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Aquí todo está bien; Chereda foo()de A. Ahora supongamos Bque se cambia para tener un foométodo, con un valor predeterminado:

interface B { 
    default void foo() { ... }
}

Ahora, cuando vayamos a recompilar C, el compilador nos dirá que no sabe qué comportamiento heredar foo(), por lo que Ctiene que anularlo (y podría elegir delegar en A.super.foo()si quisiera conservar el mismo comportamiento). Pero, ¿y si Bhabía cometido su defecto finaly Ano está bajo el control del autor de C? Ahora Cestá irremediablemente roto; no se puede compilar sin anular foo(), pero no se puede anular foo()si fue final en B.

Este es sólo un ejemplo, pero el punto es que la finalidad de los métodos es realmente una herramienta que tiene más sentido en el mundo de las clases de herencia única (generalmente que se combinan con el comportamiento), que en el de las interfaces que simplemente contribuyen con el comportamiento y pueden multiplicarse. heredado. Es demasiado difícil razonar sobre "qué otras interfaces podrían mezclarse en el eventual implementador", y permitir que un método de interfaz sea final probablemente causaría estos problemas (y no explotarían no en la persona que escribió la interfaz, sino en el mal usuario que intenta implementarlo.)

Otra razón para no permitirlos es que no significarían lo que usted cree que significan. Sólo se considera una implementación predeterminada si la clase (o sus superclases) no proporciona una declaración (concreta o abstracta) del método. Si un método predeterminado fuera final, pero una superclase ya implementara el método, el método predeterminado se ignoraría, lo que probablemente no es lo que el autor predeterminado esperaba al declararlo final. (Este comportamiento de herencia es un reflejo del centro de diseño para métodos predeterminados: evolución de la interfaz. Debería ser posible agregar un método predeterminado (o una implementación predeterminada a un método de interfaz existente) a las interfaces existentes que ya tienen implementaciones, sin cambiar el comportamiento de las clases existentes que implementan la interfaz, garantizando que las clases que ya funcionaban antes de que se agregaran los métodos predeterminados funcionarán de la misma manera en presencia de métodos predeterminados).

Brian Goetz avatar May 05 '2014 16:05 Brian Goetz

En la lista de correo de lambda hay muchas discusiones al respecto . Uno de los que parece contener mucha discusión sobre todo eso es el siguiente: Sobre la visibilidad del método de interfaz variada (era Defensores finales) .

En esta discusión, Talden, el autor de la pregunta original , hace algo muy similar a su pregunta:

La decisión de hacer públicos todos los miembros de la interfaz fue realmente una decisión desafortunada. Que cualquier uso de la interfaz en el diseño interno exponga detalles privados de implementación es un gran problema.

Es difícil de solucionar sin agregar algunos matices oscuros o que rompan la compatibilidad al lenguaje. Una ruptura de compatibilidad de esa magnitud y posible sutileza se consideraría inadmisible, por lo que debe existir una solución que no rompa el código existente.

¿Podría ser viable reintroducir la palabra clave 'paquete' como especificador de acceso? La ausencia de un especificador en una interfaz implicaría acceso público y la ausencia de un especificador en una clase implica acceso a paquetes. No está claro qué especificadores tienen sentido en una interfaz, especialmente si, para minimizar la carga de conocimiento de los desarrolladores, tenemos que asegurarnos de que los especificadores de acceso signifiquen lo mismo tanto en la clase como en la interfaz, si están presentes.

En ausencia de métodos predeterminados, habría especulado que el especificador de un miembro en una interfaz tiene que ser al menos tan visible como la interfaz misma (para que la interfaz pueda implementarse en todos los contextos visibles), con métodos predeterminados que no lo son. tan seguro.

¿Ha habido alguna comunicación clara sobre si esto es siquiera una posible discusión dentro del alcance? En caso contrario, ¿debería celebrarse en otro lugar?

Finalmente la respuesta de Brian Goetz fue:

Sí, esto ya se está explorando.

Sin embargo, permítanme establecer algunas expectativas realistas: las características de lenguaje/VM tienen un tiempo de entrega largo, incluso las que parecen triviales como esta. El momento de proponer nuevas ideas de características de lenguaje para Java SE 8 prácticamente ha pasado.

Entonces, lo más probable es que nunca se implementó porque nunca formó parte del alcance. Nunca se propuso a tiempo para ser considerado.

En otra acalorada discusión sobre los métodos de defensa final sobre el tema, Brian volvió a decir :

Y has obtenido exactamente lo que deseabas. Eso es exactamente lo que añade esta característica: herencia múltiple de comportamiento. Por supuesto, entendemos que la gente los utilizará como rasgos. Y hemos trabajado arduamente para garantizar que el modelo de herencia que ofrecen sea lo suficientemente simple y limpio como para que las personas puedan obtener buenos resultados al hacerlo en una amplia variedad de situaciones. Al mismo tiempo, hemos optado por no empujarlos más allá de los límites de lo que funciona de manera simple y limpia, y eso lleva a reacciones de "oh, no fuiste lo suficientemente lejos" en algunos casos. Pero en realidad, la mayor parte de este hilo parece quejarse de que el vaso está lleno apenas en un 98%. ¡Tomaré ese 98% y seguiré adelante!

Entonces esto refuerza mi teoría de que simplemente no era parte del alcance ni de su diseño. Lo que hicieron fue proporcionar suficiente funcionalidad para abordar los problemas de la evolución de la API.

Edwin Dalorzo avatar May 04 '2014 15:05 Edwin Dalorzo

Será difícil encontrar e identificar "LA" respuesta, por las razones mencionadas en los comentarios de @EJP: Hay aproximadamente 2 (+/- 2) personas en el mundo que pueden dar la respuesta definitiva . Y en caso de duda, la respuesta podría ser simplemente algo así como "Apoyar los métodos finales predeterminados no parecía merecer el esfuerzo de reestructurar los mecanismos internos de resolución de llamadas". Esto es especulación, por supuesto, pero al menos está respaldado por evidencias sutiles, como esta Declaración (de una de las dos personas) en la lista de correo de OpenJDK :

"Supongo que si se permitieran los métodos" predeterminados finales ", es posible que sea necesario reescribirlos desde la invocación interna especial a la interfaz de invocación visible para el usuario".

y hechos triviales como que un método simplemente no se considera un método (realmente) final cuando es un defaultmétodo, como se implementa actualmente en el método Method::is_final_method en OpenJDK.

De hecho, es difícil encontrar más información realmente "autorizada", incluso con búsquedas web excesivas y leyendo registros de confirmación. Pensé que podría estar relacionado con posibles ambigüedades durante la resolución de llamadas a métodos de interfaz con la invokeinterfaceinstrucción y las llamadas a métodos de clase, correspondientes a la invokevirtualinstrucción: Para la invokevirtualinstrucción, puede haber una búsqueda simple en vtable , porque el método debe ser heredado de una superclase, o implementado directamente por la clase. Por el contrario, una invokeinterfacellamada debe examinar el sitio de llamada respectivo para saber a qué interfaz se refiere realmente (esto se explica con más detalle en la página InterfaceCalls de HotSpot Wiki). Sin embargo, finallos métodos no se insertan en vtable en absoluto o reemplazan las entradas existentes en vtable (consulte klassVtable.cpp. Línea 333 ) y, de manera similar, los métodos predeterminados reemplazan las entradas existentes en vtable (consulte klassVtable.cpp, Línea 202 ). Por lo tanto, la razón real (y por lo tanto, la respuesta) debe estar oculta más profundamente dentro de los (bastante complejos) mecanismos de resolución de llamadas al método, pero tal vez estas referencias se consideren útiles, aunque solo sea para otros que logren derivar la respuesta real. a partir de ese.

Marco13 avatar May 04 '2014 12:05 Marco13

No creo que sea necesario especificar finalun método de interfaz conveniente, aunque estoy de acuerdo en que puede ser útil, pero aparentemente los costos han superado los beneficios.

Lo que se supone que debe hacer, de cualquier manera, es escribir un javadoc adecuado para el método predeterminado, mostrando exactamente lo que el método puede hacer y lo que no. De esa manera, las clases que implementan la interfaz "no tienen permitido" cambiar la implementación, aunque no hay garantías.

Cualquiera podría escribir un Collectionprograma que se adhiera a la interfaz y luego haga cosas con métodos que sean absolutamente contrarios a la intuición; no hay forma de protegerse de eso, aparte de escribir pruebas unitarias extensas.

skiwi avatar May 04 '2014 12:05 skiwi