switch method java interface java-8 default-method

method - public default java



Java8: ¿Por qué está prohibido definir un método predeterminado para un método de java.lang.Object (5)

Los métodos predeterminados son una nueva herramienta en nuestra caja de herramientas de Java. Sin embargo, traté de escribir una interfaz que define una versión default del método toString . Java me dice que esto está prohibido, ya que los métodos declarados en java.lang.Object pueden no ser ed default . ¿Por qué es este el caso?

Sé que existe la regla de "la clase base siempre gana", por lo que de forma predeterminada (juego de palabras), cualquier implementación default de un método de Object se sobrescribirá por el método de Object todos modos. Sin embargo, no veo ninguna razón por la cual no debería haber una excepción para los métodos de Object en la especificación. Especialmente para toString podría ser muy útil tener una implementación predeterminada.

Entonces, ¿cuál es la razón por la cual los diseñadores de Java decidieron no permitir que default métodos default anulen los métodos de Object ?


El razonamiento es muy simple, es porque Object es la clase base para todas las clases de Java. Entonces, incluso si tenemos el método Object definido como método predeterminado en alguna interfaz, será inútil porque siempre se usará el método Object. Es por eso que para evitar confusiones, no podemos tener métodos predeterminados que estén anulando los métodos de la clase Object.


Está prohibido definir métodos predeterminados en interfaces para métodos en java.lang.Object , ya que los métodos predeterminados nunca serían "alcanzables".

Los métodos de interfaz predeterminados se pueden sobrescribir en las clases que implementan la interfaz y la implementación de clase del método tiene una precedencia mayor que la implementación de la interfaz, incluso si el método se implementa en una superclase. Dado que todas las clases heredan de java.lang.Object , los métodos en java.lang.Object tendrían precedencia sobre el método predeterminado en la interfaz y se invocarán en su lugar.

Brian Goetz de Oracle proporciona algunos detalles más sobre la decisión de diseño en esta publicación de la lista de correo .


Este es otro de los problemas de diseño del lenguaje que parece "obviamente una buena idea" hasta que empiezas a cavar y te das cuenta de que realmente es una mala idea.

Este correo tiene mucho sobre el tema (y también sobre otros temas). Hubo varias fuerzas de diseño que convergieron para llevarnos al diseño actual:

  • El deseo de mantener el modelo de herencia simple;
  • El hecho de que una vez que mira más allá de los ejemplos obvios (por ejemplo, convirtiendo AbstractList en una interfaz), se da cuenta de que heredar equals / hashCode / toString está fuertemente ligado a herencia y estado únicos, y las interfaces son heredadas y sin estado de forma múltiple;
  • Que potencialmente abrió la puerta a algunos comportamientos sorprendentes.

Ya has tocado el objetivo de "mantenerlo simple"; las reglas de herencia y resolución de conflictos están diseñadas para ser muy simples (las clases ganan con las interfaces, las interfaces derivadas ganan con las superinterfaces y cualquier otro conflicto se resuelve con la clase implementadora). Por supuesto, estas reglas podrían modificarse para hacer una excepción, pero Creo que cuando empiece a tirar de esa cuerda encontrará que la complejidad incremental no es tan pequeña como podría pensar.

Por supuesto, hay algún grado de beneficio que justificaría una mayor complejidad, pero en este caso no está allí. Los métodos de los que estamos hablando son iguales, hashCode y toString. Estos métodos son todos intrínsecamente sobre el estado del objeto, y es la clase que posee el estado, no la interfaz, quien está en la mejor posición para determinar qué significa igualdad para esa clase (especialmente porque el contrato para la igualdad es bastante fuerte; ver Eficaz Java para algunas consecuencias sorprendentes); los escritores de interfaz están demasiado lejos.

Es fácil sacar el ejemplo de AbstractList ; Sería maravilloso si pudiéramos deshacernos de AbstractList y poner el comportamiento en la interfaz de la List . Pero una vez que se mueve más allá de este ejemplo obvio, no hay muchos otros buenos ejemplos que se puedan encontrar. En la raíz, AbstractList está diseñado para herencia individual. Pero las interfaces deben diseñarse para herencia múltiple.

Además, imagina que estás escribiendo esta clase:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { // Implementation of Foo, that does NOT override equals }

El escritor de Foo mira los supertipos, no ve la implementación de iguales, y concluye que para obtener la igualdad de referencia, todo lo que necesita hacer es heredar iguales de Object . Luego, la próxima semana, el mantenedor de la biblioteca para Bar "amablemente" agrega una implementación de equals predeterminada. Ooops! Ahora la semántica de Foo se ha roto mediante una interfaz en otro dominio de mantenimiento "de manera útil" al agregar un valor predeterminado para un método común.

Se supone que los valores predeterminados son predeterminados. Agregar un valor predeterminado a una interfaz donde no había ninguno (en cualquier lugar de la jerarquía) no debería afectar la semántica de las clases de implementación concretas. Pero si los valores predeterminados podrían "anular" los métodos de Objeto, eso no sería cierto.

Entonces, si bien parece una característica inofensiva, de hecho es bastante dañina: agrega mucha complejidad con poca expresividad incremental, y hace que sea demasiado fácil para los cambios bien intencionados e inofensivos en interfaces compiladas por separado como para socavar la semántica prevista de la implementación de clases.


No veo en la cabeza de los autores del lenguaje Java, por lo que solo podemos adivinar. Pero veo muchas razones y estoy totalmente de acuerdo con ellas en este tema.

La razón principal para introducir métodos predeterminados es poder agregar nuevos métodos a las interfaces sin romper la compatibilidad con versiones anteriores de las implementaciones anteriores. Los métodos predeterminados también se pueden usar para proporcionar métodos de "conveniencia" sin la necesidad de definirlos en cada una de las clases de implementación.

Ninguno de estos se aplica a toString y otros métodos de Object. En pocas palabras, los métodos predeterminados fueron diseñados para proporcionar el comportamiento predeterminado donde no hay otra definición. No proporcionar implementaciones que "compitan" con otras implementaciones existentes.

La regla de "la clase base siempre gana" también tiene sus razones sólidas. Se supone que las clases definen implementaciones reales , mientras que las interfaces definen implementaciones predeterminadas , que son algo más débiles.

Además, introducir CUALQUIER excepción a las reglas generales causa una complejidad innecesaria y genera otras preguntas. Object es (más o menos) una clase como cualquier otra, entonces ¿por qué debería tener un comportamiento diferente?

En general, la solución que propones probablemente traiga más inconvenientes que ventajas.


Para dar una respuesta muy pedante, solo está prohibido definir un método default para un método público de java.lang.Object . Hay 11 métodos para considerar, que se pueden categorizar de tres maneras para responder a esta pregunta.

  1. Seis de los métodos Object no pueden tener métodos default porque son final y no se pueden sobrescribir en absoluto: getClass() notify() , notifyAll() , notifyAll() , wait() , wait(long) y wait(long, int) .
  2. Tres de los métodos de Object no pueden tener métodos default por las razones expuestas anteriormente por Brian Goetz: equals(Object) , hashCode() y toString() .
  3. Dos de los métodos de Object pueden tener métodos default , aunque el valor de dichos valores por defecto es cuestionable en el mejor de los casos: clone() y finalize() .

    public class Main { public static void main(String... args) { new FOO().clone(); new FOO().finalize(); } interface ClonerFinalizer { default Object clone() {System.out.println("default clone"); return this;} default void finalize() {System.out.println("default finalize");} } static class FOO implements ClonerFinalizer { @Override public Object clone() { return ClonerFinalizer.super.clone(); } @Override public void finalize() { ClonerFinalizer.super.finalize(); } } }