Por que muitas classs Collection em Java estendem a class abstrata e implementam a interface também?

Por que muitas classs de Collection em Java estendem a class Abstract e também implementam a interface (que também é implementada pela class abstrata fornecida)?

Por exemplo, a class HashSet estende o AbstractSet e também implementa o Set , mas o AbstractSet já implementa o Set .

É uma maneira de lembrar que essa class realmente implementa essa interface.
Ele não terá nenhum efeito ruim e pode ajudar a entender o código sem passar pela hierarquia completa da class dada.

Do ponto de vista do sistema de tipos, as classs não seriam diferentes se não implementassem a interface novamente, já que as classs base abstratas já as implementam.

Isso é verdade.

A razão pela qual eles o implementam de qualquer maneira é (provavelmente) a maior parte da documentação: um HashSet é um Set . E isso é explicitado pela adição de implements Set no final, embora não seja estritamente necessário.

Note que a diferença é realmente observável usando reflection, mas eu seria duramente pressionado para produzir algum código que quebraria se o HashSet não implementasse o Set diretamente.

Isso pode não importar muito na prática, mas eu queria esclarecer que implementar explicitamente uma interface não é exatamente o mesmo que implementá-la por inheritance. A diferença está presente nos arquivos de class compilados e visíveis via reflection. Por exemplo,

 for (Class< ?> c : ArrayList.class.getInterfaces()) System.out.println(c); 

A saída mostra apenas as interfaces explicitamente implementadas pelo ArrayList , na ordem em que foram escritas na origem, que [na minha versão do Java] é:

 interface java.util.List interface java.util.RandomAccess interface java.lang.Cloneable interface java.io.Serializable 

A saída não inclui interfaces implementadas por superclasss ou interfaces que são superinterfaces daquelas que estão incluídas. Em particular, Iterable e Collection estão ausentes do acima, mesmo que ArrayList implemente implicitamente. Para encontrá-los, você deve iterar recursivamente a hierarquia de classs.

Seria lamentável se algum código lá fora usasse a reflection e dependesse de interfaces sendo explicitamente implementadas, mas isso é possível, então os mantenedores da biblioteca de collections podem relutar em mudá-la agora, mesmo que quisessem. (Há uma observação denominada Lei de Hyrum : “Com um número suficiente de usuários de uma API, não importa o que você prometer no contrato; todos os comportamentos observáveis ​​do seu sistema dependerão de alguém”.)

Felizmente essa diferença não afeta o sistema de tipos. As expressões new ArrayList<>() instanceof Iterable e Iterable.class.isAssignableFrom(ArrayList.class) ainda são avaliadas como true .

Ao contrário de Colin Hebert , não acredito que as pessoas que escreviam se importavam com a legibilidade. (Todos que acham que as bibliotecas Java padrão foram escritas por deuses impecáveis, devem procurar as fonts. Na primeira vez que fiz isso, fiquei horrorizado com a formatação de código e vários blocos copiados e colados.)

Minha aposta é que era tarde, eles estavam cansados ​​e não se importavam de qualquer maneira.

Do “Java eficaz” por Joshua Bloch:

Você pode combinar as vantagens de interfaces e classs abstratas, adicionando uma class de implementação esquelética abstrata para ir com uma interface.

A interface define o tipo, talvez fornecendo alguns methods padrão, enquanto a class esquelética implementa os methods de interface não primitivos remanescentes sobre os methods de interface primitivos. Estender uma implementação esquelética retira a maior parte do trabalho da implementação de uma interface. Este é o padrão do Modelo de Modelo .

Por convenção, as classs de implementação esquelética são chamadas de AbstractInterface que Interface é o nome da interface que elas implementam. Por exemplo:

 AbstractCollection AbstractSet AbstractList AbstractMap 

Eu também acredito que seja para maior clareza. O framework Java Collections possui uma hierarquia de interfaces que define os diferentes tipos de coleção. Ele começa com a interface Collection e depois é estendido por três subinterfaces principais Set, List e Queue. Há também o SortedSet que estende Set e BlockingQueue, estendendo a fila.

Agora, classs concretas implementando-as são mais compreensíveis se declararem explicitamente qual interface na hierarquia está implementando, embora possa parecer redundante às vezes. Como você mencionou, uma class como HashSet implementa Set, mas uma class como TreeSet, embora também estenda o AbstractSet implementa SortedSet, que é mais específico do que apenas Set. O HashSet pode parecer redundante, mas o TreeSet não é porque requer a implementação do SortedSet. Ainda assim, ambas as classs são implementações concretas e seriam mais compreensíveis se ambas seguirem certas convenções em sua declaração.

Existem até mesmo classs que implementam mais de um tipo de coleção, como LinkedList, que implementa List e Queue. No entanto, há pelo menos uma class que é um pouco “não convencional”, o PriorityQueue. Ele estende o AbstractQueue, mas não implementa explicitamente o Queue. Não me pergunte por quê. 🙂

(referência é da API do Java 5)

Na minha opinião, quando uma class implementa uma interface, ela deve implementar todos os methods presentes nela (como por padrão, eles são methods públicos e abstratos em uma interface).

Se não quisermos implementar todos os methods de interface, ele deve ser uma class abstrata.

Então, aqui, se alguns methods já estiverem implementados em alguma class abstrata implementando uma interface específica e tivermos que estender a funcionalidade para outros methods que não foram implementados, precisaremos implementar a interface original em nossa class novamente para obter os conjuntos restantes de methods. na manutenção das regras contratuais estabelecidas por uma interface.

Isso resultará em retrabalho se implementar apenas a interface e novamente sobrescrevendo todos os methods com definições de método em nossa class.

Tarde demais para resposta?

Eu estou adivinhando para validar minha resposta. Assuma o seguinte código

HashMap extends AbstractMap (não implementa o mapa)

AbstractMap implements Map

Agora imagine que algum cara random veio, Changed implementa Map para algum java.util.Map1 com exatamente o mesmo conjunto de methods que o Map

Nesta situação, não haverá nenhum erro de compilation e o jdk será compilado (o teste fora do curso falhará e detectará isso).

Agora, qualquer cliente usando HashMap como Map m = new HashMap () começará a falhar. Isso é muito a jusante.

Já que o AbstractMap, o Map etc vem do mesmo produto, portanto este argumento parece infantil (o que com toda a probabilidade é ou pode não ser), mas pense em um projeto onde a class base vem de um jar / biblioteca de terceiro etc. terceiros / equipes diferentes podem alterar sua implementação base.

Implementando a “interface” na class Child, também, o desenvolvedor tenta tornar a class auto-suficiente, prova de quebra de API.

Eu suponho que pode haver uma maneira diferente de lidar com membros do conjunto, a interface, mesmo quando o fornecimento da implementação da operação padrão não serve como um tamanho único para todos. Uma Fila circular versus Fila LIFO pode implementar a mesma interface, mas suas operações específicas serão implementadas de forma diferente, certo?

Se você tivesse apenas uma class abstrata, você não poderia criar uma class própria que também herda de outra class.