Os métodos padrão são uma nova ferramenta interessante em nossa caixa de ferramentas Java. No entanto, tentei escrever uma interface que define uma default
versão do toString
método. Java me diz que isso é proibido, uma vez que os métodos declarados em java.lang.Object
podem não ser default
editados. Por que esse é o caso?
Eu sei que existe a regra "classe base sempre vence", portanto, por padrão (pun;), qualquer default
implementação de um Object
método seria substituída pelo método de Object
qualquer maneira. No entanto, não vejo razão para que não exista uma exceção para métodos Object
na especificação. Especialmente porque toString
pode ser muito útil ter uma implementação padrão.
Então, qual é a razão pela qual os designers de Java decidiram não permitir que os default
métodos substituíssem os métodos Object
?
fonte
Respostas:
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:
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;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 livrarAbstractList
e colocar o comportamento naList
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:
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 iguaisObject
. Então, na próxima semana, o mantenedor da biblioteca do Bar "útil" adiciona umaequals
implementação padrão . Opa! Agora, a semântica deFoo
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.
fonte
hashCode
eequals
, mas acho que seria muito útiltoString
. Por exemplo, algumaDisplayable
interface pode definir umString display()
método e economizaria uma tonelada de clichê para poder definir ,default String toString() { return display(); }
emDisplayable
vez de exigir que cada umDisplayable
implementassetoString()
ou estendesse umaDisplayableToString
classe base.toString()
for baseado apenas nos métodos da interface, você pode simplesmente adicionar algo parecidodefault String toStringImpl()
à interface e substituirtoString()
em cada subclasse para chamar a implementação da interface - um pouco feia, mas funciona e melhor que nada. :) Outra maneira de fazer isso é criar algo comoObjects.hash()
,Arrays.deepEquals()
eArrays.deepToString()
. Marcou com +1 a resposta de @ BrianGoetz!default toString()
substituição em uma interface funcional nos permitiria - pelo menos - fazer algo como cuspir a assinatura da função e a classe pai do implementador. Melhor ainda, se pudéssemos trazer algumas estratégias recursivas de toString, poderíamos percorrer o fechamento para obter uma descrição realmente boa do lambda e, assim, melhorar drasticamente a curva de aprendizado do lambda.É proibido definir métodos padrão em interfaces para métodos em
java.lang.Object
, uma vez que os métodos padrão nunca seriam "alcançáveis".Os métodos de interface padrão podem ser substituídos nas classes que implementam a interface e a implementação de classe do método tem uma precedência mais alta que a implementação da interface, mesmo se o método for implementado em uma superclasse. Como todas as classes são herdadas
java.lang.Object
, os métodos emjava.lang.Object
teriam precedência sobre o método padrão na interface e seriam invocados.Brian Goetz, da Oracle, fornece mais alguns detalhes sobre a decisão de design nesta postagem da lista de discussão .
fonte
Eu não vejo na cabeça dos autores da linguagem Java, então podemos apenas adivinhar. Mas vejo muitas razões e concordo absolutamente com elas nesta edição.
O principal motivo para a introdução de métodos padrão é poder adicionar novos métodos às interfaces sem interromper a compatibilidade com versões anteriores 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 comportamento padrão onde não há outra definição. Não fornecer implementações que "competirão" com outras implementações existentes.
A regra "classe base sempre vence" também tem razões sólidas. Supõe-se que as classes definam implementações reais , enquanto as interfaces definem implementações padrão , que são um pouco mais fracas.
Além disso, a introdução de QUAISQUER exceções às regras gerais causa complexidade desnecessária e levanta outras questões. O objeto é (mais ou menos) uma classe como qualquer outra, então por que deveria ter um comportamento diferente?
No geral, a solução que você propõe provavelmente traria mais desvantagens do que profissionais.
fonte
O raciocínio é muito simples, porque Object é a classe base para todas as classes Java. Portanto, mesmo se tivermos o método de Object definido como método padrão em alguma interface, será inútil porque o método de Object será sempre usado. É por isso que, para evitar confusão, não podemos ter métodos padrão que estão substituindo os métodos da classe Object.
fonte
Para dar uma resposta muito pedante, só é proibido definir um
default
método para um método público a partir dejava.lang.Object
. Existem 11 métodos a serem considerados, que podem ser categorizados de três maneiras para responder a essa pergunta.Object
métodos não pode terdefault
métodos porque eles sãofinal
e não podem ser substituídos em tudo:getClass()
,notify()
,notifyAll()
,wait()
,wait(long)
, ewait(long, int)
.Object
métodos não pode terdefault
métodos para Pelas razões expostas por Brian Goetz:equals(Object)
,hashCode()
, etoString()
.Dois dos
Object
métodos podem terdefault
métodos, embora o valor de tais padrões seja questionável na melhor das hipóteses:clone()
efinalize()
.fonte