Являются ли интерфейсы Java-коллекции и иерархия классов плохой?
Я узнал, что в Java класс LinkedList
реализует интерфейсы Deque
и List
. И это несколько сбивало с толку.
В учебной программе по информатике я никогда не учил, что очередь может быть списком, или, точнее, очередь может вести себя как список. То есть, есть вещи, которые могут делать списки, но очереди не могут. Но список может вести себя как очередь. Например, интерфейс List
имеет следующие методы:
add(E e)
add(int index, E element)
Но Queue
имеет только следующее:
add(E e)
Поэтому ясно, что Queue
не допускается вставлять по определенному индексу, что разрешено в List
. То же самое относится к другим операциям, таким как Queue.remove()
vs. List.remove(int index)
, List.get(int index)
и Queue.peek()
. Другими словами, список представляет собой более обобщенную структуру данных и может эмулировать Queue
.
Теперь способность эмулировать отличается от наличия подмножества контракта. То есть Queue
запрещает определенные операции (индексирование) List
и позволяет определенные операции выполняются только определенным образом (вставлять только в хвост и удалять только из головы). Таким образом, Queue
действительно не делает "дополнение" к контрактам List
. Именно поэтому Queue
не расширяет рамки List
in Java, но расширяет интерфейс Collection
. Я считаю, что это также, почему это неправильно для любого класса, чтобы реализовать оба, как и Queue
контракта конфликты с договором List
(поэтому они раскошелиться из Collection
интерфейса отдельно). Однако LinkedList
реализует оба интерфейса.
Я также наткнулся на этот ответ:
Реализация LinkedList
выполняется, чтобы удовлетворить контракт Deque
, поэтому почему бы не реализовать его интерфейс?
Я до сих пор не понимаю, как мы можем сказать, что реализация LinkedList
выполняется, чтобы удовлетворить контракт Deque
. Понятие очереди не допускает вставки с произвольным индексом. Следовательно, интерфейс Queue
не имеет таких методов.
Однако мы можем только заключать контракты через интерфейсы и не можем запретить реализацию определенных методов. Будучи списком (имея "Список" в его имени), я считаю неправильным иметь методы очереди peek()
, pop()
и add(int index, E element)
в LinkedList
.
Я полагаю, вместо этого у нас должен быть отдельный класс LinkedQueue
который может связать реализацию для очереди, похожую на LinkedBlockingQueue
которая содержит связанную реализацию BlockingQueue
.
Также обратите внимание, что LinkedList
является единственным классом, который наследуется от обоих семейств списков и очередей, т.е. Нет другого класса, который реализует как List
и Queue
(AFAIK). Может ли это быть свидетельством того, что LinkedList
плохо сделано?
Я просто ошибаюсь и думаю излишне?
Ответы
Ответ 1
Вам совершенно не хватает точки программирования для интерфейса.
Если вам нужна Queue
, вы никогда не пишете:
LinkedList<String> queue = new LinkedList<>();
Потому что, вы правы, это позволит вам использовать методы без очереди. Вместо этого вы программируете на интерфейс следующим образом:
Queue<String> queue = new LinkedList<>();
Теперь у вас есть доступ к 6 методам Queue
(и всем методам Collection
). Итак, хотя LinkedList
реализует больше методов, у вас больше нет доступа к ним.
Итак, если вам нужна очередь, вы выбираете реализацию интерфейса Queue
которая наилучшим образом соответствует характеристикам производительности, хранения и доступа, которые вам нужны, например
Мне никогда не учили, что очередь может быть списком, или, точнее, очередь может вести себя как список.
Помните, что implements
определяет поведение, подобное отношениям. LinkedList
ведет себя как List
. LinkedList
ведет себя как Deque
. LinkedList
ведет себя как Queue
.
Но только потому, что LinkedList
ведет себя как все, это не означает, что List
ведет себя как Queue
или что Queue
ведет себя как List
. Они не.
Ведет себя как отношение только в одном направлении.
Ответ 2
Ответ @Andreas превосходный, поэтому мой мишень нацелен на ваши аргументы о том, что вы были или не учили:
В учебной программе по информатике я никогда не учил, что очередь может быть списком или, точнее, очередь может вести себя как список
Очередь - это не просто список, а особый вид списка с его собственными специальными свойствами и ограничениями.
То есть, есть вещи, которые могут делать списки, но очереди не могут.
Нет, List
ничего не может сделать. Он предоставляет возможности для реализации класса, и если этот класс решает их реализовать, то этот класс может делать все это.
Но список может вести себя как очередь.
Нет, List
не ведет себя; это только предполагает, что поведение и классы, которые его реализуют, могут принимать все или их подмножество или определять новые.
Ответ 3
LinkedList
- это класс, который реализует интерфейсы List
и Deque
. Каждый из этих интерфейсов определяет контракт с операциями, и в этих контрактах указывается, что должны делать эти операции. Однако не указано, как эти операции должны работать.
LinkedList
- это класс, который реализует интерфейсы List
и Deque
. Таким образом, несмотря на то, что List
суффиксов является частью имени класса LinkedList
, LinkedList
фактически является List
и Deque
, поскольку он реализует все операции, определенные в интерфейсах List
и Deque
.
Так LinkedList
является List
, а также является Deque
. Это не означает, что List
должен быть Deque
или Deque
должен быть List
.
Например, посмотрите на следующие интерфейсы:
public interface BloodDrinker {
void drinkBlood();
}
public interface FlyingInsect {
void flyAround();
}
Каждый из вышеперечисленных интерфейсов имеет одну операцию и определяет контракт. Операция drinkBlood
определяет, что должен делать BloodDrinker
, но не как. То же самое относится к FlyingInsect
: его операция flyAround
определяет, что он должен делать, но не как.
Теперь рассмотрим класс Mosquito
:
public class Mosquito implements FlyingInsect, BloodDrinker {
public void flyAround() {
// fly by moving wings,
// buzzing and bothering everyone around
}
public void drinkBlood() {
// drink blood by biting other animals:
// suck their blood and inject saliva
}
}
Теперь это означает, что Mosquito
- это и FlyingInsect
и BloodDrinker
, но почему пьяница для крови обязательно будет летающим насекомым или летающим насекомым обязательно будет пьяница крови? Например, вампиры - пьющие кровь, но не летающие насекомые, а бабочки - летающие насекомые, но не пьющие кровь.
Теперь, в отношении вашего аргумента о том, что Queue
не разрешает определенные операции List
(индексирование) и разрешает добавление/удаление на своих концах в режиме FIFO... Я не думаю, что это обоснование верное, по крайней мере в контексте Рамка коллекций Java. В контракте Deque
явно не упоминается, что разработчики никогда не смогут добавлять/удалять/проверять элементы в любом заданном индексе. Он просто говорит, что Deque
:
Линейный набор, который поддерживает вставку и удаление элементов на обоих концах.
И он также говорит, что:
Этот интерфейс определяет методы доступа к элементам на обоих концах deque.
(Акцент мой).
Несколько абзацев позже он прямо говорит, что:
В отличие от интерфейса List
, этот интерфейс не поддерживает индексированный доступ к элементам.
(Подчеркните еще раз).
Ключевая часть здесь не обеспечивает поддержку. Он никогда не запрещает разработчикам доступ к элементам через индексы. Это просто, что индексированный доступ не поддерживается через интерфейс Deque
.
Подумайте о моем примере выше: почему BloodDrinker
запретил своим разработчикам пить что-то другое, кроме крови, то есть воды? Или почему FlyingInsect
запрещает своим разработчикам двигаться не так, как летать, то есть ходить?
Итог, реализация может придерживаться столько контрактов, сколько пожелает, если эти контракты не противоречат друг другу. И, как сказано в Java (очень осторожная и тонкая формулировка, я должен признать), контракт Deque
не противоречит контракту List
, поэтому вполне может существовать класс, который реализует оба интерфейса, и это, случается, LinkedList
.
Ответ 4
Вы совершенно правы в этом и не пропустите точку вообще. Java просто сделала компромисс между правильностью и легкостью. Сделать его реализация обоих интерфейсов было непростой задачей, и тот, который был наиболее полезен для разработчиков.
Что означает подтипирование
Правильный (звуковой) подтипирование требует замены на работу, которая требуется в соответствии с LSP:
Инварианты супертипа должны быть сохранены в подтипе.
Когда мы говорим в теории типов "LinkedList - это список и очередь", мы на самом деле говорим, что LinkedList одновременно является списком и очередью, а не тем, что LinkedList можно рассматривать как список или очередь,
Существует нарушенный инвариант типа очереди здесь (что вы не можете изменять элементы в его середине), поэтому это неправильный подтипирование.
Фактический аргумент, который должен иметься, заключается в том, "требует ли очередь, чтобы элементы не могли быть изменены в середине или только чтобы их можно было изменить в концах FIFO".
Можно утверждать, что инварианты Очереди - это только то, что вы можете использовать ее в FIFO, а не в том, что вам нужно. Это не общая интерпертация очереди.
Ответ 5
Вы начинаете с слабого помещения:
Мне никогда не учили, что очередь может быть списком.
Вернемся к основам. Итак, каковы структуры данных? Вот как CLSR подходит к этому вопросу 1:
... В то время как математические множества неизменны, множества, управляемые алгоритмами, могут расти, сокращаться или иным образом меняться со временем.
Математически структуры данных являются просто множествами; динамические множества. В этом смысле очередь может быть списком. На самом деле в CLSR (10.2-3) есть проблема, которая явно требует, чтобы вы реализовали очередь, используя связанный список.
С другой стороны, объектно-ориентированное программирование - это парадигма, которая помогает программистам решать проблемы, придерживаясь определенной философии проблемы и данных. Объекты, интерфейсы и контракты являются частью этой философии. Использование этой парадигмы помогает нам реализовать абстрактную концепцию динамических множеств. Тем не менее, он поставляется со своим собственным багажом, один из которых является самой проблемой, заданной здесь.
Поэтому, если вы жалуетесь, что структуры данных в стандартной библиотеке Java не строго придерживаются конвенций, определенных для элементарных структур данных, вы правы. На самом деле нам даже не нужно смотреть дальше, чем java.util.Stack
чтобы увидеть это 2. Вам также разрешено развертывать свою собственную реализацию любым способом и использовать их вместо стандартных коллекций библиотек.
Но утверждать, что Java или его стандартная библиотека, если на то пошло, сломаны или плохо сделаны - экстраординарный claim-, вам нужно быть очень конкретным в отношении использования и четко продемонстрировать, как предполагаемый недостаток в библиотеке мешает вам достичь целей дизайна.
1 Введение в главу III, стр220
2 Sedgewick и Wayne вызывают java.util.Stack "широкий интерфейс" (стр. 160), поскольку он позволяет произвольный доступ к элементам стека;то, как полагают, не может быть установлен стек -as, определенный в элементарных данных structures-.