Pergunta

Os métodos padrão são uma nova ferramenta interessante em nossa caixa de ferramentas Java.No entanto, tentei escrever uma interface que defina um default versão do toString método.Java me diz que isso é proibido, pois os métodos declarados em java.lang.Object talvez não seja defaultEd.Por que isso acontece?

Eu sei que existe a regra "classe base sempre vence", então por padrão (trocadilho;), qualquer default implementação de um Object método seria substituído pelo método de Object de qualquer forma.No entanto, não vejo razão para que não deva haver uma exceção para métodos de Object na especificação.Especialmente para toString pode ser muito útil ter uma implementação padrão.

Então, qual é a razão pela qual os designers Java decidiram não permitir default métodos substituindo métodos de Object?

Foi útil?

Solução

Este é mais um daqueles problemas de design de linguagem que parece "obviamente uma boa ideia" até que você comece a pesquisar e perceba que na verdade é uma má ideia.

Este e-mail tem muito sobre o assunto (e sobre outros assuntos também). Houve várias forças de design que convergiram para nos trazer ao design atual:

  • O desejo de manter o modelo de herança simples;
  • O fato de que, uma vez que você olhe além dos exemplos óbvios (por exemplo, virar AbstractList em uma interface), você percebe que a herança de equals/hashCode/toString está fortemente ligada à herança e ao estado únicos, e as interfaces são herdadas múltiplas e sem estado;
  • Que potencialmente abriu a porta para alguns comportamentos surpreendentes.

Você já tocou no objetivo de “manter a simplicidade”;as regras de herança e resolução de conflitos são projetadas para serem muito simples (as classes vencem as interfaces, as interfaces derivadas vencem as superinterfaces e quaisquer outros conflitos são resolvidos pela classe de implementação). É claro que essas regras podem ser ajustadas para abrir uma exceção, mas Acho que você descobrirá, quando começar a puxar essa corda, que a complexidade incremental não é tão pequena quanto você imagina.

É claro que há algum grau de benefício que justificaria mais complexidade, mas neste caso não existe.Os métodos dos quais estamos falando aqui são equals, hashCode e toString.Esses métodos são todos intrinsecamente sobre o estado do objeto, e é a classe que possui o estado, e não a interface, quem está na melhor posição para determinar o que a igualdade significa para essa classe (especialmente porque o contrato de igualdade é bastante forte;veja Java Eficaz para algumas consequências surpreendentes);os escritores de interface estão muito distantes.

É fácil retirar o AbstractList exemplo;seria ótimo se pudéssemos nos livrar AbstractList e colocar o comportamento no List interface.Mas, uma vez ultrapassado este exemplo óbvio, não há muitos outros bons exemplos a serem encontrados.Na raiz, AbstractList foi projetado para herança única.Mas as interfaces devem ser projetadas para herança múltipla.

Além disso, imagine que você está escrevendo esta aula:

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

O Foo O escritor olha para os supertipos, não vê nenhuma implementação de iguais e conclui que, para obter igualdade de referência, tudo o que ele precisa fazer é herdar iguais de Object.Então, na próxima semana, o mantenedor da biblioteca do Bar "de forma útil" adiciona um padrão equals implementação.Ops!Agora a semântica de Foo foram quebrados por uma interface em outro domínio de manutenção "de forma útil", adicionando um padrão para um método comum.

Os padrões devem ser padrões.Adicionar um padrão a uma interface onde não havia nenhum (em qualquer lugar da hierarquia) não deve afetar a semântica das classes de implementação concretas.Mas se os padrões pudessem "substituir" os métodos Object, isso não seria verdade.

Portanto, embora pareça um recurso inofensivo, na verdade é bastante prejudicial:acrescenta muita complexidade com pouca expressividade incremental e torna muito fácil que alterações bem-intencionadas e de aparência inofensiva em interfaces compiladas separadamente prejudiquem a semântica pretendida de implementação de classes.

Outras dicas

É proibido definir métodos padrão em interfaces para métodos em java.lang.Object, já que os métodos padrão nunca seriam "acessíveis".

Os métodos de interface padrão podem ser substituídos em classes que implementam a interface e a implementação de classe do método tem uma precedência maior do que a implementação da interface, mesmo se o método for implementado em uma superclasse.Como todas as classes herdam de java.lang.Object, os métodos em java.lang.Object teria precedência sobre o método padrão na interface e seria invocado em seu lugar.

Brian Goetz, da Oracle, fornece mais alguns detalhes sobre a decisão de design neste postagem na lista de discussão.

Não vejo nada na cabeça dos autores da linguagem Java, então só podemos adivinhar.Mas vejo muitas razões e concordo absolutamente com elas nesta questão.

A principal razão para a introdução de métodos padrão é poder adicionar novos métodos às interfaces sem quebrar a compatibilidade retroativa de implementações mais antigas.Os métodos padrão também podem ser usados ​​para fornecer métodos de "conveniência" sem a necessidade de defini-los em cada uma das classes de implementação.

Nada disso se aplica a toString e outros métodos de Object.Simplificando, os métodos padrão foram projetados para fornecer o padrão comportamento onde não há outra definição.Não fornecer implementações que “concorram” com outras implementações existentes.

A regra “a classe base sempre vence” também tem razões sólidas.Supõe-se que as classes definam real implementações, enquanto as interfaces definem padrão implementações, que são um pouco mais fracas.

Além disso, a introdução de QUALQUER exceção às regras gerais causa complexidade desnecessária e levanta outras questões.Object é (mais ou menos) uma classe como qualquer outra, então por que deveria ter um comportamento diferente?

De modo geral, a solução que você propõe provavelmente traria mais desvantagens do que vantagens.

O raciocínio é muito simples, porque Object é a classe base para todas as classes Java.Portanto, mesmo que tenhamos o método do Object definido como método padrão em alguma interface, será inútil porque o método do Object sempre será usado.É por isso que, para evitar confusão, não podemos ter métodos padrão que substituam os métodos da classe Object.

Para dar uma resposta muito pedante, só é proibido definir um default método para um público método de java.lang.Object.Existem 11 métodos a serem considerados, que podem ser categorizados de três maneiras para responder a esta pergunta.

  1. Seis dos Object métodos não podem ter default métodos porque são final e não pode ser substituído de forma alguma: getClass(), notify(), notifyAll(), wait(), wait(long), e wait(long, int).
  2. Três dos Object métodos não podem ter default métodos pelas razões apresentadas acima por Brian Goetz: equals(Object), hashCode(), e toString().
  3. Dois dos Object métodos pode ter default métodos, embora o valor de tais padrões seja, na melhor das hipóteses, questionável: clone() e 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top