¿Por qué no está permitido "final" en los métodos de interfaz Java 8?

335

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

Desde la perspectiva de un diseñador de API, me hubiera gustado poder usar otros modificadores en los métodos de interfaz, por ejemplo final. Esto sería útil al agregar métodos de conveniencia, 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 finalobviamente son palabras clave contradictorias, pero la palabra clave predeterminada en sí misma no habría sido estrictamente requerida , por lo que supongo que esta contradicción es deliberada, para reflejar las sutiles diferencias entre "métodos de clase con cuerpo" (solo métodos) e "interfaz métodos con cuerpo " (métodos predeterminados), es decir, diferencias que aún no he entendido.

En algún momento, el soporte para modificadores como staticy finalen los métodos de interfaz aún no se había explorado 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: todavía no lo sabemos

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

Pregunta:

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

Lukas Eder
fuente
10
Lamento ser una manta húmeda, pero la única forma en que la pregunta expresada en el título será respondida dentro de los términos de SO es a través de una cita de Brian Goetz o el grupo de expertos de JSR. Entiendo que BG ha solicitado una discusión pública, pero eso es exactamente lo que contraviene los términos de SO, ya que está "principalmente basado en la opinión". Me parece que las responsabilidades se eluden aquí. Es el trabajo del Grupo de Expertos, y el Proceso de la Comunidad Java más amplio, estimular la discusión y proponer razones. No tan. Así que estoy votando para cerrar como "principalmente basado en la opinión".
Marqués de Lorne
2
Como todos sabemos, finalevita que un método se anule, y al ver cómo DEBES anular los métodos heredados de las interfaces, no veo por qué tendría sentido hacerlo definitivo. A menos que sea para indicar que el método es final DESPUÉS de anularlo una vez ... En ese caso, ¿tal vez haya dificultades? Si no entiendo este derecho, por favor déjame kmow. Parece interesante
Vince Emigh
22
@EJP: "Si yo puedo hacer eso, tú también puedes y responder tu propia pregunta, que por lo tanto no habría sido necesario preguntar". Eso se aplicaría a casi todas las preguntas en este foro, ¿verdad? Siempre podría buscar en Google algún tema durante 5 horas y luego aprender algo que todos los demás tienen que aprender de la misma manera. O esperamos otros dos minutos para que alguien dé una respuesta aún mejor de la que todos en el futuro (incluidos los 12 votos a favor y las 8 estrellas hasta ahora) puedan beneficiarse, porque SO está tan bien referenciado en Google. Entonces sí. Esta pregunta puede encajar perfectamente en el formulario de preguntas y respuestas de SO.
Lukas Eder
55
@VinceEmigh " ... viendo cómo DEBES anular los métodos heredados de las interfaces ... " Eso no es cierto en Java 8. Java 8 te permite implementar métodos en las interfaces, lo que significa que no tienes que implementarlos en la implementación clases Aquí, finaltendría un uso para evitar que las clases de implementación anulen la implementación predeterminada de un método de interfaz.
awksp
28
@EJP nunca se sabe: ¡ Brian Goetz puede responder !
Assylias 05 de

Respuestas:

419

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

La clave para comprender 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 existe cierta superposición entre los dos, y tratamos de acomodarnos al último donde no se interpuso en el primero, estas preguntas se entienden mejor cuando se consideran desde esta perspectiva. (Nota también que los métodos de clase se van a ser diferente de métodos de interfaz, no importa cuál es la intención, en virtud del hecho de que los métodos de interfaz pueden ser heredados se multiplican.)

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 fue la evolución de la interfaz, era un objetivo de diseño crítico que los métodos predeterminados se pudieran agregar a las interfaces después del hecho de una manera compatible con la fuente y compatible con el binario.

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

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

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

interface B { 
}

class C implements A, B { 
}

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

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

Ahora, cuando vamos a recompilar C, el compilador nos dirá que no sabe para qué comportamiento heredar foo(), por lo que Ctiene que anularlo (y podría elegir delegar A.super.foo()si quería retener el mismo comportamiento). Pero qué pasa si Bhabía hecho su defecto final, y Ano está bajo el control del autor de C? Ahora Cestá irremediablemente roto; no puede compilar sin anular foo(), pero no puede anular foo()si fue finalizado B.

Este es solo 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 combinan el estado con el comportamiento), que con las interfaces que simplemente contribuyen al 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 en la persona que escribió la interfaz, sino en el usuario pobre que intenta implementarlo)

Otra razón para no permitirlos es que no significarían lo que crees que significan. Una implementación predeterminada solo se considera si la clase (o sus superclases) no proporcionan una declaración (concreta o abstracta) del método. Si un método predeterminado fuera definitivo, pero una superclase ya implementara el método, el predeterminado sería ignorado, lo que probablemente no sea lo que el autor predeterminado esperaba al declararlo como final. (Este comportamiento de herencia es un reflejo del centro de diseño para los 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,

Brian Goetz
fuente
88
¡Fantástico verte responder preguntas sobre las nuevas características del lenguaje! Es muy útil obtener aclaraciones sobre la intención y los detalles del diseño al descubrir cómo deberíamos usar las nuevas funciones. ¿Hay otras personas que estuvieron involucradas con el diseño contribuyendo a SO, o simplemente lo estás haciendo tú mismo? Seguiré sus respuestas bajo la etiqueta java-8. Me gustaría saber si hay otras personas que hacen lo mismo para poder seguirlas también.
Cortado
10
@Shorn Stuart Marks ha estado activo en la etiqueta java-8. Jeremy Manson ha publicado en el pasado. También recuerdo haber visto mensajes de Joshua Bloch, pero no puedo encontrarlos en este momento.
Assylias
1
Felicitaciones por proponer métodos de interfaz predeterminados como una forma mucho más elegante de lograr lo que C # hace con sus métodos de extensión mal concebidos y bastante mal implementados. En cuanto a esta pregunta, la respuesta sobre la imposibilidad de resolver un conflicto de nombres resuelve el problema, pero el resto de las razones proporcionadas fueron filología poco convincente. (Si quiero que un método de interfaz sea final, entonces debes suponer que debo tener mis propias razones para querer impedir que alguien proporcione una implementación diferente a la mía.)
Mike Nakis
1
Aún así, la resolución de conflictos de nombres podría haberse logrado de una manera similar a como se hace en C #, al agregar el nombre de la interfaz particular que se implementa a la declaración del método de implementación. Entonces, lo que llevo a casa de todo esto es que los métodos de interfaz predeterminados no pueden ser finales en Java porque eso habría requerido modificaciones adicionales a la sintaxis, con lo que no se sintió bien. (Demasiado c-sharpish tal vez?)
Mike Nakis
18
@Probar Las clases abstractas siguen siendo la única forma de introducir el estado o implementar los métodos Object principales. Los métodos predeterminados son para comportamiento puro ; Las clases abstractas son para el comportamiento junto con el estado.
Brian Goetz
43

En la lista de correo lambda hay muchas discusiones al respecto . Uno de los que parece contener mucha discusión sobre todas esas cosas es el siguiente: Visibilidad del método de interfaz variada (fue 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 una decisión desafortunada. Que cualquier uso de la interfaz en el diseño interno expone los detalles privados de la implementación es importante.

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

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

En ausencia de métodos predeterminados, habría especulado que el especificador de un miembro en una interfaz debe ser al menos tan visible como la interfaz en sí (para que la interfaz se pueda implementar en todos los contextos visibles), con métodos predeterminados que no son muy seguro

¿Ha habido alguna comunicación clara sobre si esto es incluso una posible discusión dentro del alcance? Si no, debería celebrarse en otro lugar.

Finalmente, la respuesta de Brian Goetz fue:

Sí, esto ya está siendo explorado.

Sin embargo, permítame establecer algunas expectativas realistas: las funciones de lenguaje / VM tienen un tiempo de espera largo, incluso las que parecen triviales como esta. El tiempo para proponer nuevas ideas de características de lenguaje para Java SE 8 ya pasó.

Por lo tanto, lo más probable es que nunca se implementó porque nunca fue parte del alcance. Nunca fue propuesto a tiempo para ser considerado.

En otra acalorada discusión sobre los métodos de defensa final sobre el tema, Brian dijo nuevamente :

Y has obtenido exactamente lo que deseaste. Eso es exactamente lo que agrega esta característica: herencia múltiple de comportamiento. Por supuesto, entendemos que las personas los usarán 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 elegido no empujarlos más allá del límite de lo que funciona de manera simple y limpia, y eso lleva a reacciones de "aw, no fuiste lo suficientemente lejos" en algún caso. Pero realmente, la mayor parte de este hilo parece estar refunfuñando que el vidrio está lleno al 98%. ¡Tomaré ese 98% y seguiré con eso!

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

Edwin Dalorzo
fuente
44
Veo que debería haber incluido el antiguo nombre, "métodos de defensa", en mi odisea en Google esta mañana. +1 por desenterrar esto.
Marco13
1
Agradable desenterrar hechos históricos. Sus conclusiones se alinean bien con la respuesta oficial
Lukas Eder
No veo por qué rompería la compatibilidad con versiones anteriores. final no estaba permitido antes. ahora permiten privado, pero no protegido. myeh ... private no puede implementar ... una interfaz que extienda otra interfaz podría implementar partes de una interfaz principal, pero tiene que exponer la capacidad de sobrecarga a otros ...
mmm
17

Será difícil encontrar e identificar "la" respuesta, por las razones de fuerza mencionados en los comentarios de @EJP: Hay aproximadamente 2 (+/- 2) personas en el mundo que puede dar la respuesta definitiva en absoluto . Y en caso de duda, la respuesta podría ser algo así como "Apoyar los métodos predeterminados finales no parecía valer la pena 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 ", podrían necesitar una reescritura desde la invocación interna especial a la interfaz de invocación visible para el usuario".

y hechos triviales como ese, 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.

Además, la información realmente "autoritaria" es realmente difícil de encontrar, 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 las llamadas a métodos de interfaz con las invokeinterfacellamadas de instrucción y y método de clase, que corresponde a la invokevirtualinstrucción: Para la invokevirtualinstrucción, puede haber un simple vtable de búsqueda, ya que el método sea debe ser heredado de una superclase, o implementado por la clase directamente. En contraste con eso, una invokeinterfacellamada debe examinar el sitio de la llamada respectiva para averiguar a qué interfaz se refiere realmente esta llamada (esto se explica con más detalle en la página InterfaceCalls de HotSpot Wiki). Sin embargo,finallos métodos no se insertan en la vtable en absoluto, o reemplazan las entradas existentes en la vtable (consulte klassVtable.cpp. Línea 333 ), y de manera similar, los métodos predeterminados están reemplazando las entradas existentes en la vtable (consulte klassVtable.cpp, Línea 202 ) . Entonces, la razón real (y, por lo tanto, la respuesta) debe ocultarse más profundamente dentro de los mecanismos de resolución de llamadas del método (bastante complejo), pero tal vez estas referencias se considerarán útiles, pero solo para otros que logran obtener la respuesta real a partir de ese.

Marco13
fuente
Gracias por la interesante idea. La pieza de John Rose es un rastro interesante. Sin embargo, todavía no estoy de acuerdo con @EJP. Como contraejemplo, vea mi respuesta a una pregunta muy interesante, de estilo muy similar, de Peter Lawrey . Que es posible desenterrar hechos históricos, y siempre estoy contento de encontrarlos aquí en desbordamiento de pila (dónde si no?). Por supuesto, su respuesta sigue siendo especulativa, y no estoy 100% convencido de que los detalles de implementación de JVM serían la razón final (juego de palabras) para que JLS se escriba de una u otra manera ...
Lukas Eder
@LukasEder Claro, este tipo de preguntas son interesantes y, en mi humilde opinión, se ajustan al patrón de preguntas y respuestas. Creo que hay dos puntos cruciales que causaron la disputa aquí: el primero es que pediste "la razón". Esto puede en muchos casos simplemente no estar oficialmente documentado. Por ejemplo, no hay ninguna "razón" mencionada en el JLS por la que no hay mensajes sin firmar int, sin embargo, consulte stackoverflow.com/questions/430346 ... ...
Marco13
... ... La segunda es que solo solicitó "citas autorizadas", lo que reduce el número de personas que se atreven a escribir una respuesta de "unas pocas docenas" a ... "aproximadamente cero". Aparte de eso, no estoy seguro de cómo se entrelazan el desarrollo de la JVM y la escritura de la JLS, es decir, hasta qué punto el desarrollo influye en lo que está escrito en la JLS, pero ... evitaré cualquier especulación aquí; -)
Marco13
1
Todavía descanso mi caso. Vea quién ha respondido a mi otra pregunta :-) Ahora estará siempre claro con una respuesta autorizada, aquí en Stack Overflow, por qué se decidió no admitir synchronizedlos defaultmétodos.
Lukas Eder
3
@LukasEder Ya veo, lo mismo aquí. ¿Quién podría haber esperado eso? Las razones son bastante convincentes, particularmente para esta finalpregunta, y es algo humillante que nadie más pareciera pensar en ejemplos similares (o, tal vez, algunos pensaron en estos ejemplos, pero no se sintieron lo suficientemente autoritarios como para responder). Entonces, ahora (lo siento, tengo que hacer esto :) se pronuncia la última palabra.
Marco13
4

No creo que sea necesario especificar finalun método de interfaz de conveniencia, 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 javadoc adecuado para el método predeterminado, mostrando exactamente qué es y qué no se permite hacer. De esa manera, las clases que implementan la interfaz "no están permitidas" para cambiar la implementación, aunque no hay garantías.

Cualquiera podría escribir una Collectionque se adhiera a la interfaz y luego haga cosas en los métodos que son absolutamente contrarios a la intuición, no hay forma de protegerse de eso, aparte de escribir pruebas unitarias extensas.

skiwi
fuente
2
Los contratos Javadoc son una solución válida para el ejemplo concreto que he enumerado en mi pregunta, pero la pregunta no es realmente sobre el caso de uso del método de interfaz de conveniencia. La pregunta es sobre una razón autorizada por la que finalse ha decidido que no se permita en los interfacemétodos Java 8 . Una relación costo / beneficio insuficiente es un buen candidato, pero hasta ahora, eso es especulación.
Lukas Eder
0

Agregamos defaultpalabras clave a nuestro método dentro de un interfacecuando sabemos que la clase que amplía interfacepuede o no overridenuestra implementación. Pero, ¿qué sucede si queremos agregar un método que no queremos que anule ninguna clase de implementación? Bueno, hay dos opciones disponibles para nosotros:

  1. Agrega un default finalmétodo.
  2. Agrega un staticmétodo.

Ahora, Java dice que si tenemos una classimplementación de dos o más de interfacesmanera que tengan un defaultmétodo con exactamente el mismo nombre y firma del método, es decir, que estén duplicados, entonces debemos proporcionar una implementación de ese método en nuestra clase. Ahora, en el caso de los default finalmétodos, no podemos proporcionar una implementación y estamos atascados. Y es por eso que la finalpalabra clave no se usa en las interfaces.

Ashutosh
fuente