No Java 8, as interfaces podem conter métodos implementados, métodos estáticos e os chamados métodos "padrão" (que as classes de implementação não precisam substituir).
Na minha opinião (provavelmente ingênua), não havia necessidade de violar interfaces como essa. As interfaces sempre foram um contrato que você deve cumprir, e esse é um conceito muito simples e puro. Agora é uma mistura de várias coisas. Na minha opinião:
- métodos estáticos não pertencem a interfaces. Eles pertencem a classes de utilidade.
- Os métodos "padrão" não deveriam ter sido permitidos nas interfaces. Você sempre pode usar uma classe abstrata para esse fim.
Em resumo:
Antes do Java 8:
- Você pode usar classes abstratas e regulares para fornecer métodos estáticos e padrão. O papel das interfaces é claro.
- Todos os métodos em uma interface devem ser substituídos pela implementação de classes.
- Você não pode adicionar um novo método em uma interface sem modificar todas as implementações, mas isso é realmente uma coisa boa.
Após o Java 8:
- Praticamente não há diferença entre uma interface e uma classe abstrata (exceto herança múltipla). De fato, você pode emular uma classe regular com uma interface.
- Ao programar as implementações, os programadores podem esquecer de substituir os métodos padrão.
- Há um erro de compilação se uma classe tentar implementar duas ou mais interfaces com um método padrão com a mesma assinatura.
- Ao adicionar um método padrão a uma interface, toda classe de implementação herda automaticamente esse comportamento. Algumas dessas classes podem não ter sido projetadas com essa nova funcionalidade em mente, e isso pode causar problemas. Por exemplo, se alguém adicionar um novo método padrão
default void foo()
a uma interfaceIx
, a classeCx
implementandoIx
e tendo umfoo
método privado com a mesma assinatura não será compilada.
Quais são as principais razões para essas mudanças importantes e que novos benefícios (se houver) eles acrescentam?
java
programming-languages
interfaces
java8
Senhor Smith
fonte
fonte
@Deprecated
categoria! métodos estáticos são uma das construções mais abusadas em Java, devido à ignorância e preguiça. Muitos métodos estáticos geralmente significam programadores incompetentes, aumentam o acoplamento em várias ordens de magnitude e são um pesadelo para testar e refatorar a unidade quando você percebe por que é uma má ideia!Respostas:
Um bom exemplo motivador para métodos padrão está na biblioteca padrão Java, onde agora você tem
ao invés de
Eu não acho que eles poderiam ter feito isso de outra maneira sem mais de uma implementação idêntica de
List.sort
.fonte
IEnumerable<Byte>.Append
para juntá-los eCount
, em seguida, ligue e diga-me como os métodos de extensão resolvem o problema. SeCountIsKnown
eCount
eram membros deIEnumerable<T>
, o retorno deAppend
poderia anunciarCountIsKnown
se as coleções constituintes o fizessem, mas sem esses métodos, isso não é possível.A resposta correta é encontrada na documentação da Java , que afirma:
Essa tem sido uma fonte de dor de longa data em Java, porque as interfaces tendem a ser impossíveis de evoluir depois que são tornadas públicas. (O conteúdo da documentação está relacionado ao documento ao qual você vinculou em um comentário: Evolução da interface por meio de métodos de extensão virtual .) Além disso, a rápida adoção de novos recursos (por exemplo, lambdas e as novas APIs de fluxo) só pode ser feita estendendo o interfaces de coleções existentes e fornecendo implementações padrão. Quebrar a compatibilidade binária ou introduzir novas APIs significaria que vários anos se passariam até que os recursos mais importantes do Java 8 estivessem em uso comum.
A razão para permitir métodos estáticos nas interfaces é novamente revelada pela documentação: [isso] facilita a organização de métodos auxiliares em suas bibliotecas; você pode manter métodos estáticos específicos para uma interface na mesma interface, em vez de em uma classe separada. Em outras palavras, classes de utilidade estática como
java.util.Collections
agora podem (finalmente) ser consideradas um antipadrão, em geral (é claro que nem sempre ). Meu palpite é que adicionar suporte a esse comportamento foi trivial quando os métodos de extensão virtual foram implementados, caso contrário, provavelmente não teria sido feito.Em uma nota semelhante, um exemplo de como esses novos recursos podem ser benéficos é considerar uma classe que recentemente me incomodou
java.util.UUID
,. Realmente não oferece suporte para os tipos 1, 2 ou 5 de UUID e não pode ser facilmente modificado para isso. Também está preso a um gerador aleatório predefinido que não pode ser substituído. A implementação do código para os tipos de UUID não suportados requer uma dependência direta de uma API de terceiros e não de uma interface, ou a manutenção do código de conversão e o custo da coleta de lixo adicional. Com métodos estáticos,UUID
poderia ter sido definido como uma interface, permitindo implementações reais de terceiros das peças ausentes. (SeUUID
originalmente fosse definido como uma interface, provavelmente teríamos algum tipo de desajeitadoUuidUtil
classe com métodos estáticos, o que também seria horrível.) Muitas APIs principais do Java são degradadas por não se basearem em interfaces, mas a partir do Java 8 o número de desculpas por esse mau comportamento diminuiu felizmente.Não é correto dizer que [t] praticamente não há diferença entre uma interface e uma classe abstrata , porque as classes abstratas podem ter estado (isto é, declarar campos) enquanto as interfaces não. Portanto, não é equivalente a herança múltipla ou mesmo herança ao estilo mixin. Mixins apropriados (como as características do Groovy 2.3 ) têm acesso ao estado. (O Groovy também suporta métodos de extensão estáticos.)
Também não é uma boa ideia seguir o exemplo de Doval , na minha opinião. Uma interface deve definir um contrato, mas não deve impor o contrato. (De qualquer maneira, não em Java.) A verificação adequada de uma implementação é de responsabilidade de um conjunto de testes ou de outra ferramenta. A definição de contratos pode ser feita com anotações e o OVal é um bom exemplo, mas não sei se ele suporta restrições definidas nas interfaces. Esse sistema é viável, mesmo que não exista atualmente. (As estratégias incluem a personalização em tempo de compilação
javac
do processador de anotaçõesAPI e geração de bytecode em tempo de execução.) Idealmente, os contratos seriam aplicados em tempo de compilação e, na pior das hipóteses, usando um conjunto de testes, mas meu entendimento é de que a imposição de tempo de execução é desaprovada. Outra ferramenta interessante que pode auxiliar a programação de contratos em Java é o Checker Framework .fonte
default
os métodos não pode substituirequals
,hashCode
etoString
. Uma análise de custo / benefício muito informativa sobre por que isso não é permitido pode ser encontrada aqui: mail.openjdk.java.net/pipermail/lambda-dev/2013-March/…equals
um únicohashCode
método virtual e um único , pois existem dois tipos diferentes de igualdade que as coleções podem precisar testar e os itens que implementariam várias interfaces podem ficar presos a requisitos contratuais conflitantes. Ser capaz de usar listas que não serão alteradas comohashMap
chaves é útil, mas também há momentos em que seria útil armazenar coleções nashashMap
quais as coisas correspondiam com base na equivalência e não no estado atual [equivalência implica estado e imutabilidade correspondentes ] .Porque você só pode herdar uma classe. Se você possui duas interfaces cujas implementações são complexas o suficiente para precisar de uma classe base abstrata, essas duas interfaces são mutuamente exclusivas na prática.
A alternativa é converter essas classes base abstratas em uma coleção de métodos estáticos e transformar todos os campos em argumentos. Isso permitiria a qualquer implementador da interface chamar os métodos estáticos e obter a funcionalidade, mas é uma enorme quantidade de clichê em uma linguagem que já é muito detalhada.
Como um exemplo motivador de por que ser capaz de fornecer implementações em interfaces pode ser útil, considere esta interface Stack:
Não há como garantir que, quando alguém implementa a interface,
pop
lançará uma exceção se a pilha estiver vazia. Poderíamos aplicar essa regra separando-apop
em dois métodos: umpublic final
método que impõe o contrato e umprotected abstract
método que executa o popping real.Não apenas garantimos que todas as implementações respeitem o contrato, como também os libertamos de verificar se a pilha está vazia e lançando a exceção. É uma grande vitória! ... exceto pelo fato de termos que mudar a interface para uma classe abstrata. Em um idioma com herança única, é uma grande perda de flexibilidade. Isso torna suas possíveis interfaces mutuamente exclusivas. Ser capaz de fornecer implementações que dependem apenas dos métodos de interface resolveria o problema.
Não tenho certeza se a abordagem do Java 8 para adicionar métodos a interfaces permite adicionar métodos finais ou métodos abstratos protegidos, mas eu sei que a linguagem D permite e fornece suporte nativo para Design by Contract . Não existe perigo nessa técnica, pois
pop
é final, portanto, nenhuma classe de implementação pode substituí-la.Quanto às implementações padrão de métodos substituíveis, presumo que todas as implementações padrão adicionadas às APIs Java dependam apenas do contrato da interface à qual foram adicionadas, portanto, qualquer classe que implemente corretamente a interface também se comportará corretamente com as implementações padrão.
Além disso,
Isso não é verdade, pois você não pode declarar campos em uma interface. Qualquer método que você escreve em uma interface não pode confiar em nenhum detalhe de implementação.
Como um exemplo a favor de métodos estáticos nas interfaces, considere classes de utilitários como Coleções na API Java. Essa classe existe apenas porque esses métodos estáticos não podem ser declarados em suas respectivas interfaces.
Collections.unmodifiableList
também poderia ter sido declarado naList
interface e seria mais fácil de encontrar.fonte
Stack
interface e deseje garantir que quandopop
for chamado com uma pilha vazia, uma exceção seja lançada. Dados métodos abstratosboolean isEmpty()
eprotected T pop_impl()
, você pode implementarfinal T pop() { isEmpty()) throw PopException(); else return pop_impl(); }
Isso impõe o contrato a TODOS os implementadores.static
.Talvez a intenção fosse fornecer a capacidade de criar classes mixin , substituindo a necessidade de injetar informações ou funcionalidades estáticas por meio de uma dependência.
Essa ideia parece relacionada a como você pode usar métodos de extensão em C # para adicionar funcionalidade implementada às interfaces.
fonte
list.sort(ordering);
formulário conveniente .IEnumerable
interface em C #, poderá ver como a implementação de métodos de extensão para essa interface (comoLINQ to Objects
faz) adiciona funcionalidade a todas as classes que implementamIEnumerable
. Foi isso que eu quis dizer com adicionar funcionalidade.Os dois propósitos principais que vejo nos
default
métodos (alguns casos de uso atendem aos dois propósitos):Se fosse apenas o segundo objetivo, você não veria isso em uma nova interface como essa
Predicate
. Todas as@FunctionalInterface
interfaces anotadas precisam ter exatamente um método abstrato para que um lambda possa implementá-lo. Adicionadodefault
métodos comoand
,or
,negate
são apenas utilidade, e você não é suposto a substituí-los. No entanto, às vezes os métodos estáticos se saem melhor .Quanto à extensão de interfaces existentes - mesmo lá, alguns novos métodos são apenas açúcar de sintaxe. Métodos de
Collection
comostream
,forEach
,removeIf
- basicamente, é só utilitário que você não precisa substituir. E depois existem métodos comospliterator
. A implementação padrão é abaixo do ideal, mas ei, pelo menos o código é compilado. Apenas recorra a isso se sua interface já estiver publicada e amplamente utilizada.Quanto aos
static
métodos, acho que os outros o abordam muito bem: permite que a interface seja sua própria classe de utilidade. Talvez possamos nos livrarCollections
no futuro do Java?Set.empty()
iria balançar.fonte