Este es otro de esos problemas de diseño de lenguaje que parece "obviamente una buena idea" hasta que comienzas a cavar y te das cuenta de que en realidad 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 miras más allá de los ejemplos obvios (p. Ej., Convertirte
AbstractList
en una interfaz), te das cuenta de que heredar equals / hashCode / toString está fuertemente vinculado a la herencia y al estado individuales, y las interfaces se heredan y no tienen estado;
- Que potencialmente abrió la puerta a algunos comportamientos sorprendentes.
Ya ha tocado el objetivo "mantenerlo simple"; las reglas de herencia y resolución de conflictos están diseñadas para ser muy simples (las clases ganan interfaces, las interfaces derivadas ganan superinterfaces y cualquier otro conflicto es resuelto por la clase implementadora). Por supuesto, estas reglas podrían modificarse para hacer una excepción, pero Creo que descubrirá cuando comience a tirar de esa cuerda, 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 aquí son equals, hashCode y toString. Todos estos métodos son intrínsecamente sobre el estado del objeto, y es la clase propietaria del estado, no la interfaz, quien está en la mejor posición para determinar qué significa la igualdad para esa clase (especialmente porque el contrato para la igualdad es bastante fuerte; ver Efectivo Java por algunas consecuencias sorprendentes); los escritores de interfaces están demasiado lejos.
Es fácil sacar el AbstractList
ejemplo; Sería maravilloso si pudiéramos deshacernos de él AbstractList
y poner el comportamiento en la List
interfaz. Pero una vez que va 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 única. Pero las interfaces deben estar diseñadas para la 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 Foo
escritor 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 Object
. Luego, la próxima semana, el responsable de mantenimiento de la biblioteca para Bar agrega "útilmente" una equals
implementación predeterminada . Ooops! Ahora la semántica de Foo
se ha roto por una interfaz en otro dominio de mantenimiento "útilmente" agregando un valor predeterminado para un método común.
Se supone que los valores predeterminados son valores 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 pudieran "anular" los métodos Object, eso no sería cierto.
Entonces, si bien parece una característica inofensiva, de hecho es bastante dañina: agrega mucha complejidad para poca expresividad incremental y hace que sea demasiado fácil para los cambios bien intencionados e inofensivos para interfaces compiladas por separado para socavar la semántica prevista de la implementación de clases.