Почему LinkedHashSet <E>расширяет HashSet <e>и реализует Set <E> - PullRequest
26 голосов
/ 30 января 2010

Открыл исходный код LinkedHashSet сегодня и обнаружил кое-что интересное:

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

Вопрос в том, зачем им и "расширяет HashSet", и "реализует Set", когда HashSet уже есть Set?

Ответы [ 6 ]

31 голосов
/ 19 февраля 2010

Я спросил Джоша Блоха, и он сообщил мне, что это было ошибкой. Раньше он думал, что в этом есть какая-то ценность, но с тех пор он «увидел свет». Очевидно, что сопровождающие JDK не посчитали, что стоит отступить позже.

6 голосов
/ 30 января 2010

Им не нужно , чтобы явно написать implements Set<E>. Они сделали это для удобства чтения.

4 голосов
/ 30 января 2010

Есть еще одна причина; рассмотрим следующую 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()));
 }
}

Который выводит следующий текст (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]

Обратите внимание, что в Class1 не определены явные интерфейсы, поэтому Class # getInterfaces () не включает эти интерфейсы, в то время как Class2 делает. Использование этого становится понятным только в этой программе: -

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> 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);
 }
}

Какие выходы: -

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)

Хотя Class1 неявно реализует MyInterface, но созданный прокси - нет.

Следовательно, если мы хотим создать динамический прокси, который реализует все интерфейсы для объекта, который имеет неявное наследование интерфейса, то единственный способ сделать это в общем случае - это пройти суперклассы до java.lang.Object, как а также обход всех реализованных интерфейсов и их суперклассов (помните, что Java поддерживает множественное наследование интерфейсов), что звучит не очень эффективно, хотя гораздо проще (и быстрее) явно именовать интерфейсы, так как я предполагаю, что они устанавливаются во время компиляции .

Так, что использует рефлексию и прокси? RMI для одного ...

Следовательно, да, это удобство, но нет, оно, конечно, не является избыточным: помните, что эти классы были тщательно разработаны и реализованы Джошем Блохом, поэтому я подозреваю, что они были явно запрограммированы таким образом, чтобы работающие в сети прокси-заглушки и скелеты работали как они.

2 голосов
/ 30 января 2010

Хороший улов, ставить java.io.Serializable тоже не нужно.

1 голос
/ 30 января 2010

Возможно, это как-то связано с тем, как генерируется javadoc. Вы знаете, как Java API сообщает вам все конкретные классы, которые препятствуют интерфейсу или наследуются от других классов? Хотя я согласен с тем, что во время выполнения это избыточно, я вижу, как это может облегчить автоматическую генерацию Javadoc. Конечно, это просто дикое предположение.

1 голос
/ 30 января 2010

Это избыточно. Вы могли бы обойтись без implements Set<E>.

...