¿Por qué LinkedHashSet amplía HashSet e implementa Set

Abrió un código fuente LinkedHashSet hoy y encontró algo interesante:

public class LinkedHashSet extends HashSet implements Set, Cloneable, java.io.Serializable { 

La pregunta es: ¿por qué necesitan tanto “extends HashSet” como “implements Set” cuando HashSet ya es el Set?

Le pregunté a Josh Bloch, y él me informa que fue un error. Solía ​​pensar, hace mucho tiempo, que tenía algún valor, pero desde “vio la luz”. Claramente, los mantenedores de JDK no han considerado que esto valga la pena retirarse más tarde.

No necesitaban escribir explícitamente los implements Set . Lo hicieron por legibilidad.

Hay otra razón; considere el siguiente progtwig de Java:

 package example; import java.io.Serializable; import java.util.Arrays; public class Test { public static interface MyInterface { void foo(); } public static class BaseClass implements MyInterface, Cloneable, Serializable { @Override public void foo() { System.out.println("BaseClass.foo"); } } public static class Class1 extends BaseClass { @Override public void foo() { super.foo(); System.out.println("Class1.foo"); } } static class Class2 extends BaseClass implements MyInterface, Cloneable, Serializable { @Override public void foo() { super.foo(); System.out.println("Class2.foo"); } } public static void main(String[] args) { showInterfacesFor(BaseClass.class); showInterfacesFor(Class1.class); showInterfacesFor(Class2.class); } private static void showInterfacesFor(Class< ?> clazz) { System.out.printf("%s --> %s\n", clazz, Arrays.toString(clazz .getInterfaces())); } } 

Que produce el siguiente texto (java 6u16):

 class example.Test$BaseClass --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable] class example.Test$Class1 --> [] class example.Test$Class2 --> [interface example.Test$MyInterface, interface java.lang.Cloneable, interface java.io.Serializable] 

Observe cómo Class1 no tiene interfaces explícitas definidas, por lo que la clase # getInterfaces () no incluye esas interfaces, mientras que Class2 sí lo hace. El uso de esto solo se vuelve claro en este progtwig:

 package example; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import example.Test.BaseClass; import example.Test.Class1; import example.Test.Class2; public class Test2 extends Test { public static void main(String[] args) { MyInterface c1 = new Class1(); MyInterface c2 = new Class2(); // Note the order... MyInterface proxy2 = createProxy(c2); proxy2.foo(); // This fails with an unchecked exception MyInterface proxy1 = createProxy(c1); proxy1.foo(); } private static  T createProxy(final T obj) { final InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.printf("About to call %s() on %s\n", method .getName(), obj); return method.invoke(obj, args); } }; return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), handler); } } 

Qué salidas: –

 About to call foo() on example.Test$Class2@578ceb BaseClass.foo Class2.foo Exception in thread "main" java.lang.ClassCastException: $Proxy1 cannot be cast to example.Test$MyInterface at example.Test2.main(Test2.java:23) 

Mientras que Class1 implementa implícitamente MyInterface, pero el proxy creado no lo hace.

Por lo tanto, si quisiéramos crear un proxy dynamic que implemente todas las interfaces para un objeto que tenga herencia de interfaz implícita, entonces la única manera de hacerlo genéricamente sería hacer retroceder las superclases hasta java.lang.Object, así como caminar todas las interfaces implementadas y sus superclases (recuerde que Java admite herencia de múltiples interfaces), que no suena muy eficiente, mientras que es mucho más fácil (y más rápido) nombrar las interfaces explícitamente, ya que supongo que están configuradas en tiempo de comstackción.

Entonces, ¿qué usa la reflexión y los proxies? RMI por uno …

Por lo tanto, sí, es una conveniencia, pero ciertamente no es redundante: recuerde que estas clases fueron cuidadosamente diseñadas e implementadas por Josh Bloch, así que sospecho que fueron explícitamente progtwigdas de esta manera para que los trozos de red proxy y los esqueletos funcionen como lo hacen .

Es redundante Podría prescindir de los implements Set .

Quizás tiene algo que ver con la forma en que se genera javadoc. ¿Sabes cómo la API de Java te dice todas las clases concretas que imponen una interfaz o heredan de otras clases? Si bien estoy de acuerdo en que en tiempo de ejecución es redundante, puedo ver cómo esto podría facilitar la generación automática de javadoc. Esto es solo una conjetura salvaje, por supuesto.

Buena captura, no necesitaban poner java.io.Serializable tampoco.