簡體   English   中英

為什么 List 中沒有 tail() 或 head() 方法來獲取最后一個或第一個元素?

[英]Why no tail() or head() method in List to get last or first element?

最近和同事討論了為什么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 方法不是一個好主意是否有特定的原因?

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

Java Collections Framework由Joshua Bloch編寫。 他的API設計原則之一是: 高功率重量比

tail()head()可以通過get()size() ,因此沒有必要將tail()head()到非常通用的接口java.util.List 一旦用戶使用這些方法,您就沒有機會刪除它們,您必須永遠保留這些不必要的方法。 那很糟。

如果要遞歸處理列表(通常是函數編程中使用的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);
}

據我所知, List沒有element方法。 但是, LinkedListgetFirst()getLast() ,它們就像你描述的那樣。

在我看來,尾巴和頭部更熟悉具有功能背景的人。 當你開始傳遞函數時,它們是非常有用的,這就是大多數函數式語言實現它們的原因,甚至還有快捷符號來引用它們,比如haskell甚至是scala(即使它不是那么實用,我知道)
在“(幾乎)所有東西都是一個對象,但方法是以程序的方式制作”java世界,當傳遞函數至少很難並且總是很尷尬時,頭/尾方法不是那么有用。
例如,檢查quicksort的這個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 )看起來完全不同,它是低級別的,並且不依賴於分離頭部和尾部。
注意:下一句話是完全主觀的,基於我的個人經驗,可能被證明是錯誤的,但我認為這是真的:
函數式編程中的大多數算法都依賴於頭/尾,在程序編程中,您依賴於訪問給定位置的元素

在良好的API設計中總會有選擇。 許多方法可以添加到API中,但是,您必須找到使API可用於大多數人並使其過於混亂和冗余之間的細微差別。 實際上,您可以實現tail方法,因為您已經以高效的方式顯示了大多數List實現,並且LinkedList已經有了getLast()方法。

您應該使用列表,以使其更容易。 當你在一個列表上使用遞歸時,你必須這樣想……這個列表有一個頭(第一個元素)和一個尾(除了頭之外的所有其他元素)。 使用遞歸,你需要在頭部做你想做的事,然后在尾部調用 function,這樣你總是有一個 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);
        }
    }
}

您可以在 Stream 上獲得這些列表:

myList.stream().findFirst() // Optional<T>, return empty for empty list

尾巴(傳統意義)

myList.stream().skip(1).collect(toList()) // or don't collect & continue with a Stream

最后一個(如果列表是無限的,可能很危險:):

myList.stream().reduce((a,b) -> b) // Optional<T>, return empty for empty list

peekLast方法已在Deque接口中定義。
此外,deque必須具備這樣的功能。 因此,在List或任何其他接口中定義它是沒有意義的。
分割功能很方便。 如果您需要隨機訪問,那么您應該實現List 如果您需要有效地訪問尾部,那么您應該實現Deque 您可以輕松地實現它們(LinkedList實際上是這樣做的)。

head()通過list.iterator()。next(),list.get(0)等提供。

如果列表與尾指針雙重鏈接,或者基於數組等,則提供tail()是合理的。這些方面都沒有為Lis​​t接口本身指定。 否則它可能具有O(N)性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM