Почему нет метода tail() или head() в List для получения последнего или первого элемента?

Недавно я обсуждал с коллегой, почему интерфейс List в Java не имеет метода head() и tail().

Чтобы реализовать такую ​​функциональность, нужно написать оболочку, которая выглядит примерно так:

public E head() {
 if (underlyingList == null || underlyingList.isEmpty())
  return null;

 return underlyingList.get(0);
}


public E tail() {
 if (underlyingList == null || underlyingList.isEmpty())
  return null;

 return underlyingList.get(underlyingList.size()-1);
}

Мне не известны все реализации List, но я предполагаю, что по крайней мере в LinkedList и ArrayList должно быть довольно тривиально получить последний и первый элемент (постоянное время).

Итак, вопрос:

Есть ли какая-то конкретная причина, почему не рекомендуется использовать хвостовой метод для любой реализации List?

Ответы

Ответ 1

Структура коллекций Java написана Джошуа Блохом. Один из его принципов проектирования API: Отношение высокой мощности к весу.

tail() и head() могут быть реализованы с помощью get() и size(), поэтому нет необходимости добавлять tail() и head() к очень общему интерфейсу java.util.List. Когда пользователи используют эти методы, у вас нет возможности их удалить, и вы должны постоянно поддерживать эти ненужные методы. Это плохо.

Ответ 2

Список inteface имеет subList, который почти head и tail. Вы можете обернуть его следующим образом.

public List head(List list) {
    return list.subList(0, 1);
}

public List tail(List list) {
    return list.subList(1, list.size());
}

Ответ 3

Если вы хотите обработать список рекурсивно, что часто используется для функции head/tail в функциональном программировании, вы можете использовать Iterator.

Integer min(Iterator<Integer> iterator) {
    if ( !iterator.hasNext() ) return null;
    Integer head = iterator.next();
    Integer minTail = min(iterator);
    return minTail == null ? head : Math.min(head, minTail);
}

Ответ 4

Насколько я могу судить, List не имеет метода element. LinkedList, однако, имеет getFirst() и getLast(), которые действуют, как вы описываете.

Ответ 5

в хорошем дизайне API всегда есть выбор. есть много методов, которые могут быть добавлены в API, однако вам нужно найти тонкую грань между тем, чтобы использовать API для большинства людей и сделать его слишком загроможденным и избыточным. как бы то ни было, вы можете реализовать метод tail, как вы показали эффективным способом для большинства реализаций List, а LinkedList уже имеет метод getLast().

Ответ 6

По моему скромному мнению, хвост и голова более знакомы с людьми с функциональным фоном. Когда вы начинаете передавать функции вокруг, они невероятно полезны, поэтому большинство функциональных языков им реализованы и даже имеют ярлык, чтобы ссылаться на них, например, в haskell или даже в scala (даже если это не так функционально, я знаю)
В "почти" все есть объект, но методы производятся процедурно "java world, когда прохождение вокруг функций по крайней мере сложно и всегда неудобно, методы head/tail не так полезны.
Например, проверьте реализацию haskell в quicksort:

quicksort :: Ord a => [a] -> [a]
quicksort []     = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
    where
        lesser  = filter (< p) xs
        greater = filter (>= p) xs

Он, среди прочего, полагается на способность легко разделять голову и хвост, но также и на возможность фильтровать коллекцию с использованием предиката. Реализация java (проверка http://www.vogella.de/articles/JavaAlgorithmsQuicksort/article.html) выглядит совершенно по-другому, она является более низким уровнем и не полагается на разделение головы и хвоста.
Примечание. Следующее предложение полностью субъективно и основано на моем личном опыте и может быть доказано неправильно, но я думаю, что это правда:
Большинство алгоритмов в функциональном программировании полагаются на head/tail, при процедурном программировании вы полагаетесь на доступ к элементу в заданной позиции

Ответ 7

peekLast метод уже определен в интерфейсе Deque.
Более того, для deque обязательно иметь такую ​​функциональность. Таким образом, нет смысла определять его в List или любом другом интерфейсе.
Это просто удобно разделить функциональность. Если вам нужен произвольный доступ, вы должны реализовать List. Если вам нужно получить доступ к хвосту эффективно, вы должны реализовать Deque. Вы можете легко реализовать оба из них (LinkedList делает это, на самом деле).

Ответ 8

head() предоставляется через list.iterator(). next(), list.get(0) и т.д.

Разумно предоставить tail(), если список дважды связан с указателем на хвост или на основе массива и т.д. Ни один из этих аспектов не указан для самого интерфейса List. В противном случае он может иметь производительность O (N).