概述

在这个教程中,我们将探讨Java中的List接口。我们将讨论List提供的方法、其实现以及使用场景。

Java列表简介

Java是一种面向对象的语言,大多数问题都涉及对象及其关联的行为或操作。当我们需要同时处理同一类型多个对象时,就需要用到集合。在Java中,List此处链接)是一种保证元素顺序并允许重复的集合实现

列表方法与使用

让我们来看看List接口最重要的方法,并了解如何使用它们。这里以ArrayList实现为例。

3.1 添加元素

使用void add(E element)方法向列表添加新元素:

@Test
public void givenAFruitList_whenAddNewFruit_thenFruitIsAdded(){
    List fruits = new ArrayList();
    assertEquals("Unexpected number of fruits in the list, should have been 0", 0, fruits.size());
        
    fruits.add("Apple");
    assertEquals("Unexpected number of fruits in the list, should have been 1", 1, fruits.size());
}

3.2 检查列表是否包含元素

我们可以使用boolean contains(Object o)方法检查列表是否包含特定元素:

@Test
public void givenAFruitList_whenContainsFruit_thenFruitIsInTheList(){
    List fruits = new ArrayList();
        
    fruits.add("Apple");
    assertTrue("Apple should be in the fruit list", fruits.contains("Apple"));
    assertFalse("Banana should not be in the fruit list", fruits.contains("Banana"));
}

3.3 检查列表是否为空

使用boolean isEmpty()方法检查列表是否为空:

@Test
public void givenAnEmptyFruitList_whenEmptyCheck_thenListIsEmpty(){
    List fruits = new ArrayList();
    assertTrue("Fruit list should be empty", fruits.isEmpty());
        
    fruits.add("Apple");
    assertFalse("Fruit list should not be empty", fruits.isEmpty());
}

3.4 遍历列表

如果我们想遍历列表,可以使用ListIterator listIterator()方法:

@Test
public void givenAFruitList_whenIterateOverIt_thenFruitsAreInOrder(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); // fruit at index 0
    fruits.add("Orange");// fruit at index 1
    fruits.add("Banana");// fruit at index 2
    int index = 0;
    for (Iterator it = fruits.listIterator(); it.hasNext(); ) {
        String fruit = it.next();
        assertEquals("Fruits should be in order", fruits.get(index++), fruit);
    }
}

3.5 删除元素

使用boolean remove(Object o)方法从列表中删除元素:

@Test
public void givenAFruitList_whenRemoveFruit_thenFruitIsRemoved(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); 
    fruits.add("Orange");
    assertEquals("Unexpected number of fruits in the list, should have been 2", 2, fruits.size());
        
    fruits.remove("Apple");
    assertEquals("Unexpected number of fruits in the list, should have been 1", 1, fruits.size());
}

3.6 修改元素

使用E set(int index, E element)方法在指定索引处修改列表中的元素:

@Test
public void givenAFruitList_whenSetFruit_thenFruitIsUpdated(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); 
    fruits.add("Orange");
        
    fruits.set(0, "Banana");
    assertEquals("Fruit at index 0 should be Banana", "Banana", fruits.get(0));
}

3.7 获取列表大小

使用int size()方法获取列表的大小:

List fruits = new ArrayList();
        
fruits.add("Apple"); 
fruits.add("Orange");
assertEquals("Unexpected number of fruits in the list, should have been 2", 2, fruits.size());

3.8 排序列表

我们有许多方法对列表进行排序。这里,我们将看看如何使用List接口的default void sort(Comparator c)方法来实现。

这个方法需要一个比较器作为参数。我们可以提供自然顺序的比较器:

@Test
public void givenAFruitList_whenSort_thenFruitsAreSorted(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); 
    fruits.add("Orange");
    fruits.add("Banana");
        
    fruits.sort(Comparator.naturalOrder());
        
    assertEquals("Fruit at index 0 should be Apple", "Apple", fruits.get(0));
    assertEquals("Fruit at index 1 should be Banana", "Banana", fruits.get(1));
    assertEquals("Fruit at index 2 should be Orange", "Orange", fruits.get(2));
}

3.9 创建子列表

通过向ListsubList(int fromIndex, int toIndex)方法提供fromIndextoIndex参数,可以从列表中创建子列表。需要注意的是,toIndex不包括在内:

@Test
public void givenAFruitList_whenSublist_thenWeGetASublist(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); 
    fruits.add("Orange");
    fruits.add("Banana");
        
    List fruitsSublist = fruits.subList(0, 2);
    assertEquals("Unexpected number of fruits in the sublist, should have been 2", 2, fruitsSublist.size());
        
    assertEquals("Fruit at index 0 should be Apple", "Apple", fruitsSublist.get(0));
    assertEquals("Fruit at index 1 should be Orange", "Orange", fruitsSublist.get(1));
}

3.10 将列表转换为数组

我们可以使用T[] toArray(T[] a)方法创建一个包含列表元素的数组:

@Test
public void givenAFruitList_whenToArray_thenWeGetAnArray(){
    List fruits = new ArrayList();
        
    fruits.add("Apple"); 
    fruits.add("Orange");
    fruits.add("Banana");
        
    String[] fruitsArray = fruits.toArray(new String[0]);
    assertEquals("Unexpected number of fruits in the array, should have been 3", 3, fruitsArray.length);
        
    assertEquals("Fruit at index 0 should be Apple", "Apple", fruitsArray[0]);
    assertEquals("Fruit at index 1 should be Orange", "Orange", fruitsArray[1]);
    assertEquals("Fruit at index 2 should be Banana", "Banana", fruitsArray[2]);
}

4. List实现

让我们看看Java中常用的List接口实现。

4.1 ArrayList

ArrayListList接口的可变数组实现。它实现了所有可选操作,并允许所有元素,包括null。这个类大致相当于Vector,但它是无同步的。

这是List接口最常用的实现。

4.2 CopyOnWriteArrayList

CopyOnWriteArrayListArrayList的线程安全版本。这个类的所有修改操作(如添加、设置等)都会在底层数组上创建一个新的副本

这个实现因其内置的线程安全能力而被使用。

4.3 LinkedList

LinkedListListDeque接口的双向链表实现。它实现了所有可选操作,并允许所有元素(包括null)。

4.4 抽象列表实现

这里有两个抽象实现,提供了List接口的骨架实现,帮助减少扩展和定制List所需的工作:

  • AbstractList - 用于内部状态的“随机访问”数据存储(如数组)
  • AbstractSequentialList - 用于内部状态的“顺序访问”数据存储(如链表)

4.5 其他具体列表实现

以下是两个值得讨论的具体实现:

  • Vector - 实现了一个可增长的对象数组。像数组一样,它使用整数索引包含组件。这个类是同步的。如果不需要线程安全的实现,建议使用ArrayList替换Vector详情)。
  • Stack - 表示一个后进先出(LIFO)对象栈。它继承自Vector类,并提供了五个额外的操作,使向量可以被视为栈。

Java还提供了一些特定的List实现,它们的行为类似于上述讨论的一种实现。

5. 总结

在这篇文章中,我们深入研究了Java的List接口及其实现。当我们只关心元素顺序并允许重复时,List是首选的集合类型。由于它们内部处理扩容,因此比数组更受欢迎。

如往常一样,代码片段可以在GitHub上找到。