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

StackOverflow https://stackoverflow.com//questions/24016962

Pregunta

Los métodos predeterminados son una nueva y agradable herramienta en nuestra caja de herramientas de Java.Sin embargo, intenté escribir una interfaz que defina un default versión de la toString método.Java me dice que esto está prohibido, ya que los métodos declarados en java.lang.Object puede no ser defaulted.¿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 default implementación de un Object El método sería sobrescrito por el método de Object de todos modos.Sin embargo, no veo ninguna razón por la que no debería haber una excepción para los métodos de Object en la especificación.Especialmente para toString Podría resultar muy útil tener una implementación predeterminada.

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

¿Fue útil?

Solución

Este es otro de esos problemas de diseño de lenguaje que parece "obviamente una buena idea" hasta que empiezas a investigar 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 simple el modelo de herencia;
  • El hecho de que una vez que miras más allá de los ejemplos obvios (por ejemplo, girar AbstractList en una interfaz), te das cuenta de que heredar equals/hashCode/toString está fuertemente ligado a la herencia y el estado únicos, y que las interfaces se heredan múltiples veces y no tienen estado;
  • Que potencialmente abrió la puerta a algunos comportamientos sorprendentes.

Ya has mencionado 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 a las interfaces, las interfaces derivadas ganan a las superinterfaces y cualquier otro conflicto lo resuelve la clase implementadora). Por supuesto, estas reglas podrían modificarse para hacer una excepción, pero Creo que, cuando empieces a tirar de ese hilo, descubrirás que la complejidad incremental no es tan pequeña como podrías pensar.

Por supuesto, hay cierto grado de beneficio que justificaría una mayor complejidad, pero en este caso no existe.Los métodos de los que estamos hablando aquí son iguales, hashCode y toString.Todos estos métodos tienen que ver intrínsecamente con 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 de igualdad es bastante fuerte;consulte Java efectivo para conocer algunas consecuencias sorprendentes);Los escritores de interfaces están demasiado alejados.

Es fácil sacar el AbstractList ejemplo;Sería maravilloso si pudiéramos deshacernos de AbstractList y poner el comportamiento en el List interfaz.Pero una vez que se va más allá de este ejemplo obvio, no se pueden encontrar muchos otros buenos ejemplos.En la raíz, AbstractList está diseñado para herencia única.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 Foo El escritor analiza los supertipos, no ve ninguna 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 encargado de la biblioteca de Bar agrega "útilmente" un archivo predeterminado equals implementación.¡Ups!Ahora la semántica de Foo han sido rotos 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 lo había (en ningún 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 de objeto, eso no sería cierto.

Entonces, aunque parezca una característica inofensiva, en realidad es bastante dañina:agrega mucha complejidad con poca expresividad incremental, y hace que sea demasiado fácil que cambios bien intencionados y aparentemente inofensivos en interfaces compiladas por separado socaven la semántica prevista de las clases de implementación.

Otros consejos

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 clase del método tiene mayor prioridad 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ía prioridad sobre el método predeterminado en la interfaz y sería invocado en su lugar.

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

No veo la cabeza de los autores del lenguaje Java, por lo que sólo 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 se pueden utilizar para proporcionar métodos "convenientes" 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 la por defecto comportamiento donde no existe otra definición.No proporcionar implementaciones que "compitarán" con otras implementaciones existentes.

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

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

En definitiva, la solución que propone probablemente traería más desventajas que ventajas.

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

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

  1. Seis de los Object Los métodos no pueden tener default métodos porque son final y no se puede anular en absoluto: getClass(), notify(), notifyAll(), wait(), wait(long), y wait(long, int).
  2. tres de los Object Los métodos no pueden tener default métodos por las razones dadas anteriormente por Brian Goetz: equals(Object), hashCode(), y toString().
  3. dos de los Object métodos poder tener default métodos, aunque el valor de dichos valores predeterminados es, en el mejor de los casos, cuestionable: 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();
            }
        }
    }
    
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top