Una de las características más útiles de Java 8 son los nuevos default
métodos en las interfaces. Básicamente, hay 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 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 Sender
fuera 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, default
y final
obviamente 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 static
y final
en 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, static
se 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?
fuente
final
evita 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 interesantefinal
tendría un uso para evitar que las clases de implementación anulen la implementación predeterminada de un método de interfaz.Respuestas:
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:
Aquí todo está bien;
C
heredafoo()
deA
. Ahora supongamos queB
se cambia para tener unfoo
método, con un valor predeterminado:Ahora, cuando vamos a recompilar
C
, el compilador nos dirá que no sabe para qué comportamiento heredarfoo()
, por lo queC
tiene que anularlo (y podría elegir delegarA.super.foo()
si quería retener el mismo comportamiento). Pero qué pasa siB
había hecho su defectofinal
, yA
no está bajo el control del autor deC
? AhoraC
está irremediablemente roto; no puede compilar sin anularfoo()
, pero no puede anularfoo()
si fue finalizadoB
.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,
fuente
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:
Finalmente, la respuesta de Brian Goetz fue:
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 :
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.
fuente
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 :
y hechos triviales como ese, un método simplemente no se considera un método (realmente) final cuando es un
default
mé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
invokeinterface
llamadas de instrucción y y método de clase, que corresponde a lainvokevirtual
instrucción: Para lainvokevirtual
instrucció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, unainvokeinterface
llamada 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,final
los 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.fuente
int
, sin embargo, consulte stackoverflow.com/questions/430346 ... ...synchronized
losdefault
métodos.final
pregunta, 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.No creo que sea necesario especificar
final
un 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
Collection
que 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.fuente
final
se ha decidido que no se permita en losinterface
métodos Java 8 . Una relación costo / beneficio insuficiente es un buen candidato, pero hasta ahora, eso es especulación.Agregamos
default
palabras clave a nuestro método dentro de uninterface
cuando sabemos que la clase que amplíainterface
puede o nooverride
nuestra 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:default
final
método.static
método.Ahora, Java dice que si tenemos una
class
implementación de dos o más deinterfaces
manera que tengan undefault
mé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 losdefault
final
métodos, no podemos proporcionar una implementación y estamos atascados. Y es por eso que lafinal
palabra clave no se usa en las interfaces.fuente