Почему нет метода 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).