Подтвердить что ты не робот

Почему многие классы Collection в Java расширяют абстрактный класс и реализуют интерфейс?

Почему многие классы Collection в Java расширяют класс Abstract, а также реализуют интерфейс (который также реализуется данным абстрактным классом)?

Например, класс HashSet расширяет AbstractSet, а также реализует Set, но AbstractSet уже реализует Set.

4b9b3361

Ответ 1

Это способ запомнить, что этот класс действительно реализует этот интерфейс.
Он не будет иметь никакого плохого эффекта, и он может помочь понять код, не пройдя полную иерархию данного класса.

Ответ 2

С точки зрения системы типов классы не изменились бы, если бы они не реализовали интерфейс снова, поскольку абстрактные базовые классы уже реализуют их.

Это правда.

В любом случае, причина, по которой они это реализуют, - это (вероятно) документация: HashSet - это Set. И это становится явным, добавляя в конец implements Set, хотя это не является строго необходимым.

Обратите внимание, что на самом деле различие можно наблюдать с помощью отражения, но мне было бы сложно создать некоторый код, который сломался бы, если бы HashSet не реализовал Set напрямую.

Ответ 3

Это может не иметь большого значения на практике, но я хотел уточнить, что явная реализация интерфейса не совсем то же самое, что реализация его с помощью наследования. Разница присутствует в скомпилированных файлах классов и видна через отражение. Например,

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

Выходные данные показывают только интерфейсы, явно реализованные ArrayList, в том порядке, в котором они были написаны в источнике, который [в моей версии Java]:

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

Вывод не включает интерфейсы, реализованные суперклассами, или интерфейсы, которые являются суперинтерфейсами тех, которые включены. В частности, Iterable и Collection отсутствуют в приведенном выше, хотя ArrayList реализует их неявно. Чтобы найти их, вы должны рекурсивно перебрать иерархию классов.

Было бы прискорбно, если какой-то код использует отражение и зависит от явных реализаций интерфейсов, но это возможно, поэтому сопровождающие библиотеки коллекций могут неохотно менять ее сейчас, даже если они этого захотят. (Существует наблюдение, называемое законом Хайрама: "При достаточном количестве пользователей API не имеет значения, что вы обещаете в контракте; кто-то будет зависеть от всех наблюдаемых действий вашей системы".)

К счастью, эта разница не влияет на систему типов. Выражения new ArrayList<>() instanceof Iterable и Iterable.class.isAssignableFrom(ArrayList.class) прежнему оцениваются как true.

Ответ 4

В отличие от Colin Hebert, я не покупаю тех людей, которые писали, которые заботились о читаемости. (Каждый, кто думает, что стандартные библиотеки Java были написаны безупречными богами, должен взглянуть на них своими источниками. В первый раз я сделал это, я был в ужасе от форматирования кода и многочисленных копируемых блоков).

Моя ставка - это было поздно, они устали и не волновались в любом случае.

Ответ 5

Из "Эффективной Явы" Джошуа Блоха:

Вы можете объединить преимущества интерфейсов и абстрактных классов, добавив абстрактный класс реализации скелета для интерфейса.

Интерфейс определяет тип, возможно, предоставляя некоторые методы по умолчанию, в то время как скелетный класс реализует оставшиеся не примитивные методы интерфейса поверх примитивных методов интерфейса. Расширение скелетной реализации отнимает большую часть работы по реализации интерфейса. Это шаблонный метод.

По соглашению классы скелетной реализации называются AbstractInterface где Interface - это имя интерфейса, который они реализуют. Например:

AbstractCollection
AbstractSet
AbstractList
AbstractMap

Ответ 6

Я также считаю, что это для ясности. Структура Java Collections имеет довольно иерархическую структуру интерфейсов, которая определяет различные типы коллекций. Он начинается с интерфейса Collection, а затем расширен тремя основными подинтерфейсами Set, List и Queue. Существует также SortedSet, расширяющий Queue и BlockingQueue.

Теперь конкретные классы, реализующие их, более понятны, если они явно указывают, какой интерфейс реализуется в иерархической иерархии, хотя иногда он может выглядеть излишним. Как вы упомянули, такой класс, как HashSet, реализует Set, но класс, подобный TreeSet, хотя он также расширяет AbstractSet, а вместо этого устанавливает SortedSet, что более специфично, чем просто Set. HashSet может выглядеть излишним, но TreeSet не потому, что ему требуется реализовать SortedSet. Тем не менее, оба класса являются конкретными реализациями и были бы более понятными, если бы оба придерживались определенного соглашения в своей декларации.

Существуют даже классы, которые реализуют более одного типа коллекции, например LinkedList, который реализует как List, так и Queue. Тем не менее, есть один класс, по крайней мере, немного "нетрадиционный", PriorityQueue. Он расширяет AbstractQueue, но явно не реализует Queue. Не спрашивай меня, почему.:)

(ссылка из API Java 5)

Ответ 7

На мой взгляд, когда класс реализует интерфейс, он должен реализовать все имеющиеся в нем методы (поскольку по умолчанию они являются общедоступными и абстрактными методами в интерфейсе).

Если мы не хотим реализовывать все методы интерфейса, это должен быть абстрактный класс.

Итак, если некоторые методы уже реализованы в каком-то абстрактном классе, реализующем конкретный интерфейс, и мы должны расширить функциональность для других методов, которые были не реализованы, нам нужно будет снова реализовать оригинальный интерфейс в нашем классе, чтобы получить оставшийся набор методов. Это помогает в поддержании договорных правил, установленных интерфейсом.

Это приведет к переделке, если бы реализовать только интерфейс и снова переопределить все методы с помощью определений методов в нашем классе.

Ответ 8

Слишком поздно для ответа?

Я угадываю, чтобы подтвердить свой ответ. Предположим, что следующий код

HashMap extends AbstractMap (не реализует карту)

AbstractMap implements Map

Теперь представьте, что появился какой-то случайный парень, Измененный реализует карту для некоторого java.util.Map1 с точно таким же набором методов, что и Map

В этой ситуации не будет никакой ошибки компиляции, и jdk будет скомпилирован (не пройден тест теста и поймает это).

Теперь любой клиент, использующий HashMap в качестве Map m = new HashMap(), начнет сбой. Это намного ниже по течению.

Так как и AbstractMap, Map и т.д. поступает от одного и того же продукта, следовательно, этот аргумент кажется ребяческим (что, по всей вероятности, есть или может быть не так.), но подумайте о проекте, в котором базовый класс поступает из другой библиотеки jar/third-party и т.д. Затем третья сторона/другая команда может изменить свою базовую реализацию.

Внедряя "интерфейс" в классе Child, разработчик также попытается сделать сам класс достаточным, доказательство обхода API.

Ответ 9

Я предполагаю, что может быть другой способ обработать членов набора, интерфейс, даже если поставка реализации по умолчанию не является универсальной. Круговая Queue vs. LIFO Queue может реализовать один и тот же интерфейс, но их конкретные операции будут реализованы по-разному, правильно?

Ответ 10

Если у вас был только абстрактный класс, вы не могли бы создать собственный класс, который наследует и от другого класса.