Los métodos predeterminados son una buena herramienta nueva en nuestra caja de herramientas Java. Sin embargo, intenté escribir una interfaz que defina una default
versión del toString
método. Java me dice que esto está prohibido, ya que los métodos declarados en java.lang.Object
no pueden ser default
editados. ¿Por qué es este el caso?
Sé que existe la regla de "la clase base siempre gana", por lo que, de manera predeterminada (juego de palabras;), cualquier default
implementación de un Object
método sería sobrescrita 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
la especificación. Especialmente porque 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 los default
métodos anulen los métodos Object
?
fuente
Respuestas:
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:
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;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 élAbstractList
y poner el comportamiento en laList
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:
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 igualesObject
. Luego, la próxima semana, el responsable de mantenimiento de la biblioteca para Bar agrega "útilmente" unaequals
implementación predeterminada . Ooops! Ahora la semántica deFoo
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.
fuente
hashCode
yequals
, pero creo que sería muy útiltoString
. Por ejemplo, algunaDisplayable
interfaz podría definir unString display()
método, y ahorraría una tonelada de repeticiones para poder definir ,default String toString() { return display(); }
enDisplayable
lugar de requerir que cada unoDisplayable
implementetoString()
o extienda unaDisplayableToString
clase base.toString()
se basa solo en los métodos de la interfaz, simplemente podría agregar algo comodefault String toStringImpl()
a la interfaz y anulartoString()
en cada subclase para llamar a la implementación de la interfaz, un poco feo, pero funciona, y mejor que nada. :) Otra forma de hacerlo es hacer algo comoObjects.hash()
,Arrays.deepEquals()
yArrays.deepToString()
. ¡Hice +1 en la respuesta de @ BrianGoetz!default toString()
anulación en una interfaz funcional nos permitiría, al menos, hacer algo como escupir la firma de la función y la clase principal del implementador. Aún mejor, si pudiéramos aplicar algunas estrategias recursivas de String, podríamos caminar a través del cierre para obtener una muy buena descripción de la lambda, y así mejorar drásticamente la curva de aprendizaje de lambda.Está prohibido definir métodos predeterminados en interfaces para métodos en
java.lang.Object
, ya que los métodos predeterminados nunca serían "accesibles".Los métodos de interfaz predeterminados se pueden sobrescribir en las clases que implementan la interfaz y la implementación de la clase del método tiene mayor prioridad que la implementación de la interfaz, incluso si el método se implementa en una superclase. Como todas las clases heredan de
java.lang.Object
, los métodos enjava.lang.Object
tendrán prioridad 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 .
fuente
No veo en la cabeza de los autores del lenguaje Java, por lo que solo podemos adivinar. Pero veo muchas razones y estoy absolutamente 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 implementaciones anteriores. Los métodos predeterminados también pueden usarse 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 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, la introducción de CUALQUIER excepción a las reglas generales causa una complejidad innecesaria y genera otras preguntas. El objeto 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 propone probablemente traiga más inconvenientes que profesionales.
fuente
El razonamiento es muy simple, es porque Object es la clase base para todas las clases Java. Entonces, incluso si tenemos el método de Object definido como método predeterminado en alguna interfaz, será inútil porque el método de Object siempre se usará. Es por eso que para evitar confusiones, no podemos tener métodos predeterminados que anulen los métodos de la clase Object.
fuente
Para dar una respuesta muy pedante, solo está prohibido definir un
default
método para un método públicojava.lang.Object
. Hay 11 métodos a considerar, que se pueden clasificar de tres maneras para responder a esta pregunta.Object
métodos no puede tenerdefault
métodos porque sonfinal
y no puede ser anulado en absoluto:getClass()
,notify()
,notifyAll()
,wait()
,wait(long)
, ywait(long, int)
.Object
métodos no puede tenerdefault
métodos para las razones dadas anteriormente por Brian Goetz:equals(Object)
,hashCode()
, ytoString()
.Dos de los
Object
métodos pueden tenerdefault
métodos, aunque el valor de estos valores predeterminados es cuestionable en el mejor de los casos:clone()
yfinalize()
.fuente