Esse é mais um daqueles problemas de design de linguagem que parece "obviamente uma boa idéia" até você começar a cavar e perceber que é realmente uma péssima idéia.
Esse e-mail tem muito a ver com o assunto (e com outros assuntos também.) Houve várias forças de design que convergiram para nos levar ao design atual:
- O desejo de manter o modelo de herança simples;
- O fato de que, depois de examinar os exemplos óbvios (por exemplo, transformar
AbstractList
- se em uma interface), você percebe que a herança de iguais / hashCode / toString está fortemente ligada à herança e ao estado únicos, e as interfaces são herdadas e sem estado multiplicadas;
- Que potencialmente abriu a porta para alguns comportamentos surpreendentes.
Você já tocou no objetivo de "manter as coisas simples"; as regras de herança e resolução de conflitos são projetadas para serem muito simples (classes ganham interfaces, interfaces derivadas ganham superinterfaces e quaisquer outros conflitos são resolvidos pela classe de implementação.) É claro que essas regras podem ser aprimoradas para fazer 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.
Obviamente, há algum grau de benefício que justificaria mais complexidade, mas, neste caso, não existe. Os métodos que estamos falando aqui são iguais, hashCode e toString. Todos esses métodos são intrinsecamente sobre o estado do objeto, e é a classe que possui o estado, não a interface, que está na melhor posição para determinar o que significa igualdade para essa classe (especialmente porque o contrato de igualdade é bastante forte; consulte Eficaz Java por algumas conseqüências surpreendentes); os gravadores de interface são removidos demais.
É fácil retirar o AbstractList
exemplo; seria ótimo se pudéssemos nos livrar AbstractList
e colocar o comportamento na List
interface. Mas uma vez que você se move além desse exemplo óbvio, não há muitos outros bons exemplos a serem encontrados. Na raiz, AbstractList
é 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 classe:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
O Foo
escritor examina os supertipos, não vê implementação de iguais e conclui que, para obter igualdade de referência, tudo o que ele precisa fazer é herdar iguais Object
. Então, na próxima semana, o mantenedor da biblioteca do Bar "útil" adiciona uma equals
implementação padrão . Opa! Agora, a semântica de Foo
foi quebrada por uma interface em outro domínio de manutenção "útil", adicionando um padrão para um método comum.
Padrões devem ser padrões. Adicionar um padrão a uma interface onde não havia nenhum (em nenhum lugar da hierarquia) 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, é de fato bastante prejudicial: adiciona muita complexidade para pouca expressividade incremental e facilita demais para que alterações intencionais e inofensivas nas interfaces compiladas separadamente possam prejudicar a semântica pretendida para implementar classes.