【问题标题】:Why no tail() or head() method in List to get last or first element?为什么 List 中没有 tail() 或 head() 方法来获取最后一个或第一个元素?
【发布时间】:2011-09-27 12:06:23
【问题描述】:

我最近和一位同事讨论了为什么Java中的List接口没有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 实现提供 tail 方法不是一个好主意有什么具体原因吗?

【问题讨论】:

  • Deque接口中还有getFirst()getLast()
  • 正如其他人解释的 head()tail() 可以简单地实现。但我想提醒你不要使用return null 部分——除非你有充分的理由,否则不要这样做。
  • tail 传统上应该返回列表减去它的头部。这就是你在任何函数式语言中都能得到的。
  • 因混淆不正确的术语而被否决的问题。请改写问题,将“head”替换为“last”(至少,这是此处实现的 Haskell 名称)。
  • 很抱歉,这让您感到困惑,但在 java api 文档中,“head”和“tail”这两个术语被多次使用,所以我想对于这种情况,这些是正确的条款。

标签: java api list


【解决方案1】:

List 接口有subList,几乎是headtail。您可以将其包装如下

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

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

编辑

根据@Pablo Grisafi 的回答,这是一个 Java 快速排序实现——不是通用的,也不是高效的。正如预期的那样,head() 应该返回一个元素——而不是列表。

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)));
    }
}

【讨论】:

  • 不应该“head”返回一个元素而不是一个子列表? IE。 public &lt;T&gt; T head(List&lt;T&gt; list) { return list.get(0); }
  • 你也可以这样做。这大概符合headtail的常见含义
  • 终于有人知道tail()应该是什么了。
  • @averasko,我认为没有有效的方法可以在空列表上定义 head() / tail()
  • 如果你认为空列表调用head/tail是异常的,欢迎抛出异常。但请注意,许多列表处理习语(尤其是递归/函数式)认为这种情况完全有效。
【解决方案2】:

如果您想递归处理列表,这通常是函数式编程中使用的头/尾,您可以使用迭代器。

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);
}

【讨论】:

    【解决方案3】:

    以我的拙见,尾巴和头更熟悉具有功能背景的人。当您开始传递函数时,它们非常有用,这就是为什么大多数函数式语言都实现了它们,甚至有快捷表示法来引用它们,比如在 haskell 甚至 scala 中(即使它不是那么实用,我知道)
    在“(几乎)一切都是对象,但方法是以过程方式创建的”Java 世界中,当传递函数至少很困难且总是很尴尬时,头/尾方法就没那么有用了。
    例如,检查快速排序的这个 haskell 实现:

    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)看起来完全不同,它的级别更低,并且不依赖于分离头和尾。
    注意:下一句完全是主观的,基于我的个人经验,可能被证明是错误的,但我认为这是真的:
    函数式编程中的大多数算法都依赖于头/尾,在过程式编程中,您依赖于访问给定位置的元素

    【讨论】:

    • 我在回答中尝试过在 Java 中实现功能快速排序。不如 Haskell 简洁但可行。
    【解决方案4】:

    Java Collections Framework 由 Joshua Bloch 编写。他的 API 设计原则之一是:高功率重量比

    tail()head()可以通过get()size()实现,所以在一个非常通用的接口java.util.List中不需要添加tail()head()。一旦用户使用这些方法,您就没有机会删除它们,您必须永远维护这些不必要的方法。这很糟糕。

    【讨论】:

    • 问题是这些方法不是必需的 - 功能样式列表处理是 PITA 没有这些。
    • 不仅仅是 PITA,它的用处要小得多。假设我有一个惰性列表,我想得到它的尾巴。我该怎么办?此外,为了获得列表的尾部,我必须遍历所有列表来计算长度。太浪费了。
    • 这听起来像是 Oracle 用于在其数据库中没有布尔值的论点。
    【解决方案5】:

    据我所知,List 没有element 方法。但是,LinkedListgetFirst()getLast(),按照您的描述进行。

    【讨论】:

    • “Get last”与“tail”不同。
    • 该问题的tail() 定义错误,而答案重现了该错误。
    【解决方案6】:

    在良好的 API 设计中总是必须做出选择。有 很多 方法可以添加到 API,但是,您必须在使 API 对大多数人可用和使其过于混乱和多余的。事实上,您可以按照您在大多数 List 实现中展示的有效方式实现 tail 方法,并且 LinkedList 已经有一个 getLast() 方法。

    【讨论】:

    • 这个答案复制了问题中的错误,它给出了 tail() 的错误定义。 Tail 意味着列表没有头部,而不是列表的最后一个元素。
    • @LawrenceDol - 似乎您的问题在于问题,而不是这个答案,因为答案解决了所提出的问题。根据维基百科,您的解释似乎是两种有效解释之一:“列表的‘尾部’可能指的是头部之后的列表的其余部分,或者是列表中的最后一个节点。”
    • @LawrenceDol - 请删除您被误导的反对票。
    【解决方案7】:

    peekLast 方法已在 Deque 接口中定义。
    此外,deque 必须具有这样的功能。因此,没有必要在 List 或任何其他接口中定义它。
    拆分功能很方便。如果你需要随机访问,那么你应该实现List。如果您需要有效地访问尾部,那么您应该实现Deque。你可以很容易地实现它们(实际上是 LinkedList 做到了)。

    【讨论】:

    • 这个答案复制了问题中的错误,它给出了 tail() 的错误定义。 Tail 表示列表没有头部,而不是列表的最后一个元素。
    • @LawrenceDol 你的定义不是唯一的。例如,查看 Deque.peekLast the tail of this deque, or null if this deque is empty 的 javadoc。 Java 集合的文档将 tail 定义为最后一个元素。原始问题也使用此定义。
    【解决方案8】:

    您应该使用列表,以使其更容易。 当你在列表上使用递归时,你必须这样想...... 该列表有一个头部(第一个元素)和一个尾部(除了头部之外的所有其他元素)。 使用递归,你需要在头部做你想做的事,然后在尾部调用函数,这样你总是有一个 size = size - 1 的列表

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        
        list.add(11);
        list.add(12);
        list.add(0);
        list.add(3);
        list.add(1);
        list.add(4);
        list.add(11);
        
        System.out.println(countOccurrences(list, 11));
        
    }
    
    
    
    public static int countOccurrences(List<Integer> list, int n) {
        if (list.size() == 0) {//if list is empty return 0
            return 0;
        }else {
            if(list.get(0) == n) {//if head of list is equal to n add 1 and call the function on the tail of the list
                return 1 + countOccurrences(list.subList(1, list.size()), n);
            }else {//else if it's not equal to n call the function on the tail of the list without adding 1
                return countOccurrences(list.subList(1, list.size()), n);
            }
        }
    }
    

    【讨论】:

      【解决方案9】:

      head() 通过 list.iterator().next()、list.get(0) 等方式提供。

      仅当列表与尾指针双向链接或基于数组等时,才合理提供tail(),List接口本身没有指定这些方面。否则它可能会有 O(N) 的性能。

      【讨论】:

      • 这个答案也重现了问题中的错误,它给出了不正确的尾部定义。 tail 函数返回没有头的列表,因此 tail 实际上非常适合单链表,而不是数组或双向链表。
      • @Sigmax 此答案使用 OP 使用的相同定义回答了实际提出的问题。我看不出说他问错了问题,或批评回答问题的答案有什么意义。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-22
      • 2015-08-16
      • 2012-02-11
      • 1970-01-01
      • 1970-01-01
      • 2018-11-30
      • 1970-01-01
      相关资源
      最近更新 更多