¿Por qué muchas clases de Colección en Java extienden la clase abstracta e implementan también la interfaz?

¿Por qué muchas clases de Colección en Java extienden la clase Abstracta y también implementan la interfaz (que también es implementada por la clase abstracta dada)?

Por ejemplo, la clase HashSet extiende AbstractSet y también implementa Set , pero AbstractSet ya implementa Set .

Es una manera de recordar que esta clase realmente implementa esa interfaz.
No tendrá ningún efecto negativo y puede ayudar a comprender el código sin pasar por la jerarquía completa de la clase dada.

Desde la perspectiva del sistema de tipos, las clases no serían diferentes si no implementaran nuevamente la interfaz, ya que las clases base abstractas ya las implementan.

Eso es verdad

La razón por la que lo implementan de todos modos es (probablemente) mayormente documentación: un HashSet es un Set . Y eso se hace explícito agregando implements Set hasta el final, aunque no es estrictamente necesario.

Tenga en cuenta que existe una diferencia realmente observable mediante la reflexión, pero me sería difícil producir algún código que se rompería si HashSet no implementara Set directamente.

Esto puede no importar mucho en la práctica, pero quería aclarar que la implementación explícita de una interfaz no es exactamente lo mismo que implementarla por herencia. La diferencia está presente en los archivos de clase comstackdos y visible a través de la reflexión. P.ej,

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

El resultado muestra solo las interfaces explícitamente implementadas por ArrayList , en el orden en que fueron escritas en la fuente, que [en mi versión de Java] es:

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

El resultado no incluye interfaces implementadas por superclases, o interfaces que son superinterfaces de las que están incluidas. En particular, Iterable y Collection faltan de lo anterior, aunque ArrayList implementa implícitamente. Para encontrarlos, debe iterar recursivamente la jerarquía de clases.

Sería desafortunado si algún código utiliza la reflexión y depende de que las interfaces se implementen explícitamente, pero es posible, por lo que los responsables de la biblioteca de colecciones pueden ser reacios a cambiarlo ahora, incluso si quisieran. (Existe una observación llamada Ley de Hyrum : “Con un número suficiente de usuarios de una API, no importa lo que prometas en el contrato, todos los comportamientos observables de tu sistema dependerán de alguien”).

Afortunadamente esta diferencia no afecta el sistema de tipos. Las expresiones new ArrayList<>() instanceof Iterable e Iterable.class.isAssignableFrom(ArrayList.class) todavía se evalúan como true .

A diferencia de Colin Hebert , no creo que a las personas que escribieron les importe la facilidad de lectura. (Todo el mundo que piensa que las bibliotecas estándar de Java fueron escritas por dioses impecables, debería buscar sus fonts. La primera vez que lo hice me horrorizó el formato del código y numerosos bloques copiados).

Mi apuesta es que era tarde, estaban cansados ​​y no les importaba de ninguna manera.

De la “Java efectiva” por Joshua Bloch:

Puede combinar las ventajas de las interfaces y las clases abstractas agregando una clase de implementación esquelética abstracta para ir con una interfaz.

La interfaz define el tipo, quizás proporcionando algunos métodos predeterminados, mientras que la clase esquelética implementa los métodos de interfaz no primitivos restantes encima de los métodos de interfaz primitivos. La extensión de una implementación básica elimina la mayor parte del trabajo de implementar una interfaz. Este es el patrón de Método de plantilla .

Por convención, las clases de implementación esqueléticas se llaman AbstractInterface donde Interface es el nombre de la interfaz que implementan. Por ejemplo:

 AbstractCollection AbstractSet AbstractList AbstractMap 

También creo que es por claridad. El marco de Java Collections tiene una gran jerarquía de interfaces que define los diferentes tipos de colección. Comienza con la interfaz Collection y luego se extiende por tres subinterfaces principales Set, List y Queue. También hay SortedSet extendiendo Set y BlockingQueue extendiendo Queue.

Ahora, las clases concretas que los implementan son más comprensibles si establecen explícitamente qué interfaz en la jerarquía está implementando aunque a veces parezca redundante. Como mencionaste, una clase como HashSet implementa Set pero una clase como TreeSet aunque también extiende AbstractSet implementa SortedSet, que es más específico que simplemente Set. HashSet puede parecer redundante, pero TreeSet no se debe a que requiera implementar SortedSet. Aún así, ambas clases son implementaciones concretas y serían más comprensibles si ambas siguen ciertas convenciones en su statement.

Incluso hay clases que implementan más de un tipo de colección, como LinkedList, que implementa tanto List como Queue. Sin embargo, hay una clase al menos que es un poco ‘no convencional’, PriorityQueue. Extiende AbstractQueue pero no implementa explícitamente Queue. No me preguntes por qué. 🙂

(la referencia es de la API de Java 5)

En mi opinión, cuando una clase implementa una interfaz, debe implementar todos los métodos presentes en ella (ya que de manera predeterminada son métodos públicos y abstractos en una interfaz).

Si no queremos implementar todos los métodos de interfaz, debe ser una clase abstracta.

Entonces, si algunos métodos ya están implementados en alguna clase abstracta implementando una interfaz particular y tenemos que ampliar la funcionalidad para otros métodos que no se han implementado, tendremos que implementar una interfaz original en nuestra clase nuevamente para obtener los restantes métodos. Ayuda en el mantenimiento de las normas contractuales establecidas por una interfaz.

Se traducirá en una nueva versión si se implementara solo la interfaz y se reemplazaran todos los métodos con definiciones de métodos en nuestra clase.

Demasiado tarde para la respuesta?

Estoy tomando una conjetura para validar mi respuesta. Suponer el siguiente código

HashMap extends AbstractMap (no implementa Map)

AbstractMap implements Map

Ahora imagina que vino un tipo aleatorio, modificó implementos Map a algunos java.util.Map1 con exactamente el mismo conjunto de métodos que Map

En esta situación, no habrá ningún error de comstackción y se comstackrá jdk (la prueba fuera del curso fallará y se detectará).

Ahora cualquier cliente que use HashMap como Map m = new HashMap () comenzará a fallar. Esto es mucho río abajo.

Dado que AbstractMap, Map etc. provienen del mismo producto, este argumento parece infantil (que con toda probabilidad lo es o no), pero piense en un proyecto en el que la clase base proviene de una biblioteca diferente de jar / third party, etc. un tercero / equipo diferente puede cambiar su implementación base.

Al implementar la “interfaz” en la clase Child, también, el desarrollador intenta hacer que la clase sea autosuficiente, prueba de rotura API.

Supongo que podría haber una forma diferente de manejar los miembros del conjunto, la interfaz, incluso cuando el suministro de la implementación de la operación predeterminada no sirve como un ajuste único para todos. Una Queue circular contra LIFO Queue podría implementar la misma interfaz, pero sus operaciones específicas se implementarán de manera diferente, ¿verdad?

Si solo tienes una clase abstracta, no podrías crear una clase propia que también herede de otra clase.