I recently had a discussion with a collegue why the List interface in Java doesn\'t have a head()
and tail()
method.
In order to implement
Java Collections Framework is written by Joshua Bloch. One of his API design principles is: High power-to-weight ratio.
tail()
and head()
can be implemented by get()
and size()
, so it's not necessary to add tail()
and head()
to a very general interface java.util.List
. Once users use the methods, you don't have chance to remove them and you have to maintain these unnecessary methods forever. That's bad.
there are always choices one must make in good API design. there are lots of methods that could be added to the API, however, you have to find the fine line between making the API usable for most people and making it too cluttered and redundant. as it is, you can implement the tail method as you have shown in an efficient way for most List implementations, and LinkedList already has a getLast() method.
In my humble opinion, tail and head are more familiar with people with a functional background. When you start passing functions around, they are incredible useful, that's why most functional languages have them implemented and even have shortcut notation to refer to them, like in haskell or even in scala (even if it's not THAT functional, I know)
In the "(almost) everything is an object but methods are made in a procedural way" java world, when passing around functions is at least hard and always awkward, head/tail methods are not so useful.
For example, check this haskell implementation of quicksort:
quicksort :: Ord a => [a] -> [a]
quicksort [] = []
quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
where
lesser = filter (< p) xs
greater = filter (>= p) xs
It relies, among other things, on the ability to easily separate head and tail, but also on being able to filter a collection using a predicate. A java implementation (check http://www.vogella.de/articles/JavaAlgorithmsQuicksort/article.html) looks totally different, it is way lower level, and doesn't rely on separating head and tail.
Note: The next sentence is totally subjective and based on my personal experience and may be proven wrong, but I think it's true:
Most algorithms in functional programming rely on head/tail, in procedural programming you rely on accessing an element in a given position
As far as I can tell, List
doesn't have an element
method. LinkedList
, however, has getFirst()
and getLast()
, which do as you describe.
If you want to process a list recursively, which is often what head/tail are used for in functional programming, you can use an 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);
}
The List inteface has subList
which is almost head
and tail
. You can wrap it as follows
public List head(List list) {
return list.subList(0, 1);
}
public List tail(List list) {
return list.subList(1, list.size());
}
Edit
Following the answer by @Pablo Grisafi, here is a Java quick sort implementation - not generic and not efficient. As expected head()
should return an element - not list.
public class QSort {
public static List<Integer> qsort(List<Integer> list) {
if (list.isEmpty()) {
return list;
} else {
return merge(
qsort(lesser
(head(list), tail(list))),
head(list),
qsort(greater(
head(list), tail(list)))
);
}
}
private static Integer head(List<Integer> list) {
return list.get(0);
}
private static List<Integer> tail(List<Integer> list) {
return list.subList(1, list.size());
}
private static List<Integer> lesser(Integer p, List<Integer> list) {
return list.stream().filter(i -> i < p).collect(toList());
}
private static List<Integer> greater(Integer p, List<Integer> list) {
return list.stream().filter(i -> i >= p).collect(toList());
}
private static List<Integer> merge(List<Integer> lesser, Integer p, List<Integer> greater) {
ArrayList list = new ArrayList(lesser);
list.add(p);
list.addAll(greater);
return list;
}
public static void main(String[] args) {
System.out.println(qsort(asList(7, 1, 2, 3, -1, 8, 4, 5, 6)));
}
}