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


Los métodos predeterminados son una buena herramienta nueva en nuestra caja de herramientas de Java. Sin embargo, intenté escribir una interfaz que definiera 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 default ed. ¿Por qué es este el caso?

Sé que existe la regla "la clase base siempre gana", por lo que por defecto (juego de palabras ;), cualquier implementación default de un método Object sería sobrescrita por el método de Object de todos modos. Sin embargo, no veo ninguna razón por la que no debería ser 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 que los diseñadores de Java decidieron no permitir default métodos sobreescribiendo métodos de Object?

Author: gexicide, 2014-06-03

5 answers

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

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

  • El deseo de mantener el modelo de herencia simple; {[18]]}
  • El hecho de que una vez que miras más allá de los ejemplos obvios (por ejemplo, girando AbstractList en una interfaz), se da cuenta de que heredar es igual a/hashCode/toString está fuertemente ligado a la herencia única y el estado, y las interfaces se multiplican heredadas y sin estado;
  • Que potencialmente abrió la puerta a algunos comportamientos sorprendentes.

Ya has tocado el objetivo de "keep it 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 otra los conflictos son resueltos por la clase implementadora. Por supuesto, estas reglas podrían ser retocadas para hacer una excepción, pero creo que encontrarás cuando empieces a tirar de esa cadena, que la complejidad incremental no es tan pequeña como podrías pensar.

Por supuesto, hay cierto grado de beneficio que justificaría más complejidad, pero en este caso no está ahí. Los métodos de los que estamos hablando aquí son equals, hashCode y toString. Todos estos métodos son intrínsecamente acerca del objeto estado, y es la clase que posee el estado, no la interfaz, que está en la mejor posición para determinar lo que significa la igualdad para esa clase (especialmente porque el contrato para la igualdad es bastante fuerte; ver Java eficaz para algunas consecuencias sorprendentes); los escritores de interfaz están demasiado lejos.

Es fácil sacar la AbstractList ejemplo, sería fantástico si pudiéramos deshacernos de AbstractList y poner el comportamiento en el List interfaz. Pero una vez que te muevas más allá de este ejemplo obvio, no hay muchos otros buenos ejemplos. En la raíz, AbstractList está diseñado para una sola herencia. Pero las interfaces deben estar diseñadas 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 Foo mira 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 mantenedor de la biblioteca para Bar" helpfully " agrega un valor predeterminado equals aplicación. Ooops! Ahora la semántica de Foo se ha roto por una interfaz en otro dominio de mantenimiento "de manera útil" añadiendo 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 de objeto, eso no sería cierto.

Así que, si bien parece una característica inofensiva, de hecho, es bastante dañino: agrega mucha complejidad para una pequeña expresividad incremental, y lo hace demasiado fácil para que los cambios bien intencionados e inofensivos en las interfaces compiladas por separado socaven la semántica pretendida de implementar clases.

 162
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-04 21:52:46

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 una prioridad más alta 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 prioridad sobre el método predeterminado en el interfaz y ser invocado en su lugar.

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

 26
Author: jarnbjo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-03 14:09:51

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 esta cuestión.

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 usar para proporcionar métodos de "conveniencia" sin la necesidad de definirlos en cada una de las clases implementadoras.

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 débil.

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

Con todo, la solución que propones probablemente traería más contras que pros.

 3
Author: Marwin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-03 14:20:02

El razonamiento es muy simple, es porque Object es la clase base para todas las clases Java. Así que 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 estén sobreescribiendo métodos de clase de objeto.

 1
Author: Kumar Abhishek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-15 08:19:37

Para dar una respuesta muy pedante, solo está prohibido definir un método default para público método de java.lang.Object. Hay 11 métodos a considerar, que se pueden clasificar 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 pueden ser anulados en absoluto: getClass(), notify(), notifyAll(), wait(), wait(long), y wait(long, int).
  2. Tres de los métodos Object no pueden tener métodos default por las razones dado arriba por Brian Goetz: equals(Object), hashCode(), y toString().
  3. Dos de los métodos Object pueden tener default métodos, aunque el valor de tales valores predeterminados 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();
            }
        }
    }
    
 1
Author: jaco0646,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-02 01:25:28