列表interface
List是有序的Collection(有时称为序列)。列表可能包含重复的元素。除了Collection
继承的操作外,List
interface还包括以下操作:
-
Positional access
—根据元素在列表中的数字位置来操作它们。这包括get
,set
,add
,addAll
和remove
之类的方法。 -
Search
—在列表中搜索指定的对象并返回其数字位置。搜索方法包括indexOf
和lastIndexOf
。 -
Iteration
—扩展了Iterator
语义,以利用列表的 Sequences 性质。listIterator
方法提供了此行为。 -
Range-view
—sublist
方法对列表执行任意范围的操作。
Java 平台包含两个通用的List
实现。 ArrayList通常是性能较好的实现,而LinkedList在某些情况下可以提供更好的性能。
Collection Operations
假设您已经熟悉了Collection
所继承的操作,那么这些操作都可以满足您的期望。如果您不熟悉Collection
的内容,那么现在是阅读收集interface部分的好时机。 remove
操作始终从列表中删除第一次出现的指定元素。 add
和addAll
操作始终将新元素追加到列表的末尾。因此,以下成语将一个列表连接到另一个列表。
list1.addAll(list2);
这是此惯用语的一种非破坏性形式,它产生第三个List
,该第二个列表附加在第一个列表之后。
List<Type> list3 = new ArrayList<Type>(list1);
list3.addAll(list2);
请注意,该成语以其非破坏性形式利用了ArrayList
的标准转换构造函数。
这是一个示例(JDK 8 和更高版本),将一些名称聚合到List
:
List<String> list = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
像Setinterface一样,List
加强了对equals
和hashCode
方法的要求,因此可以比较两个List
对象的逻辑相等性,而不必考虑它们的实现类。如果两个List
对象包含相同 Sequences 的相同元素,则它们相等。
位置访问和搜索操作
基本的positional access
操作是get
,set
,add
和remove
。 (set
和remove
操作返回被覆盖或删除的旧值.)其他操作(indexOf
和lastIndexOf
)返回列表中指定元素的第一个或最后一个索引。
addAll
操作从指定位置插入指定Collection
的所有元素。元素按指定的Collection
的迭代器返回的 Sequences 插入。此调用是Collection
的addAll
操作的位置访问模拟。
这是一种在List
中交换两个索引值的小方法。
public static <E> void swap(List<E> a, int i, int j) {
E tmp = a.get(i);
a.set(i, a.get(j));
a.set(j, tmp);
}
当然,有一个很大的不同。这是一个多态算法:交换任何List
中的两个元素,无论其实现类型如何。这是另一个使用前面的swap
方法的多态算法。
public static void shuffle(List<?> list, Random rnd) {
for (int i = list.size(); i > 1; i--)
swap(list, i - 1, rnd.nextInt(i));
}
该算法包含在 Java 平台的Collections类中,它使用指定的随机性源随机排列指定的列表。这有点微妙:它从底部开始运行列表,反复将随机选择的元素交换到当前位置。与大多数天真的改组try不同,它是公平的(所有排列均以相同的可能性发生,并假定无偏的随机性源)且速度快(需要list.size()-1
交换)。下面的程序使用此算法以随机 Sequences 在其参数列表中打印单词。
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (String a : args)
list.add(a);
Collections.shuffle(list, new Random());
System.out.println(list);
}
}
实际上,可以使该程序更短,更快。 Arrays类具有称为asList
的静态工厂方法,该方法允许将数组视为List
。此方法不复制数组。 List
中的更改直写到数组,反之亦然。结果列表不是通用的List
实现,因为它没有实现add
和remove
操作(可选):数组不可调整大小。利用Arrays.asList
并调用库版本shuffle
(使用默认的随机性源),您将获得以下tiny program,其行为与之前的程序相同。
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = Arrays.asList(args);
Collections.shuffle(list);
System.out.println(list);
}
}
Iterators
如您所料,List
的iterator
操作返回的Iterator
将按正确的 Sequences 返回列表中的元素。 List
还提供了一个更丰富的迭代器,称为ListIterator
,它使您可以沿任一方向遍历列表,在迭代过程中修改列表并获取迭代器的当前位置。
ListIterator
继承自Iterator
的三个方法(hasNext
,next
和remove
)在两个interface中都做完全相同的事情。 hasPrevious
和previous
操作与hasNext
和next
完全相同。前一个操作引用了(隐式)光标之前的元素,而后一个操作引用了光标之后的元素。 previous
操作将光标向后移动,而next
操作将其向前移动。
这是用于向后遍历列表的标准用法。
for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) {
Type t = it.previous();
...
}
请注意前面习语中的listIterator
参数。 List
interface具有listIterator
方法的两种形式。不带参数的表单返回位于列表开头的ListIterator
。带有int
参数的表单返回位于指定索引处的ListIterator
。索引指向初始调用next
将返回的元素。初始调用previous
将返回索引为index-1
的元素。在 Long 度为n
的列表中,index
的有效值为n+1
,从0
到n
(含)。
从直觉上讲,游标始终位于两个元素之间-一个通过调用previous
返回的元素和一个通过next
调用返回的元素。 n+1
有效index
值对应于元素之间的n+1
间隙,从第一个元素之前的间隙到最后一个元素之后的间隙。下图显示了包含四个元素的列表中五个可能的光标位置。
五个可能的光标位置。
可以将对next
和previous
的呼叫混在一起,但是您必须小心一点。第一次调用previous
返回与最后一次调用next
相同的元素。同样,在对previous
的一系列调用之后,对next
的第一次调用返回与对previous
的最后一次调用相同的元素。
nextIndex
方法返回由随后调用next
返回的元素的索引,而previousIndex
返回由后续调用previous
返回的元素的索引,也就不足为奇了。这些调用通常用于报告找到某物的位置或记录ListIterator
的位置,以便可以创建另一个具有相同位置的ListIterator
。
nextIndex
返回的数字总是比previousIndex
返回的数字大 1 也就不足为奇了。这暗示了两种边界情况的行为:(1)当游标在初始元素返回-1
之前时调用previousIndex
;(2)当游标在final元素返回list.size()
之后时调用nextIndex
。为了使所有这些具体,下面是List.indexOf
的可能实现。
public int indexOf(E e) {
for (ListIterator<E> it = listIterator(); it.hasNext(); )
if (e == null ? it.next() == null : e.equals(it.next()))
return it.previousIndex();
// Element not found
return -1;
}
请注意,即使indexOf
方法正向遍历列表,它也会返回it.previousIndex()
。原因是it.nextIndex()
将返回我们要检查的元素的索引,而我们想返回我们刚刚检查的元素的索引。
Iterator
interface提供remove
操作,以从Collection
中删除next
返回的最后一个元素。对于ListIterator
,此操作将删除next
或previous
返回的最后一个元素。 ListIterator
interface提供了两个额外的操作来修改列表set
和add
。 set
方法用指定的元素覆盖next
或previous
返回的最后一个元素。以下多态算法使用set
将一个指定值的所有出现替换为另一个。
public static <E> void replace(List<E> list, E val, E newVal) {
for (ListIterator<E> it = list.listIterator(); it.hasNext(); )
if (val == null ? it.next() == null : val.equals(it.next()))
it.set(newVal);
}
在此示例中,唯一棘手的问题是val
和it.next
之间的相等性测试。您需要将val
值null
特例化以防止NullPointerException
。
add
方法将一个新元素插入到当前光标位置之前的列表中。在下面的多态算法中说明了此方法,用指定列表中包含的值序列替换所有出现的指定值。
public static <E>
void replace(List<E> list, E val, List<? extends E> newVals) {
for (ListIterator<E> it = list.listIterator(); it.hasNext(); ){
if (val == null ? it.next() == null : val.equals(it.next())) {
it.remove();
for (E e : newVals)
it.add(e);
}
}
}
Range-View Operation
range-view
操作subList(int fromIndex, int toIndex)
返回该列表部分的List
视图,其索引范围从fromIndex
(包括)到toIndex
(排除)。这个半开范围反映了典型的for
循环。
for (int i = fromIndex; i < toIndex; i++) {
...
}
正如术语“视图”所暗示的那样,返回的List
由调用subList
的List
备份,因此前者的更改反映在后者中。
此方法消除了对显式范围操作(数组通常存在的那种范围)的需要。通过传递subList
视图而不是整个List
,可以将期望List
的任何操作用作范围操作。例如,以下成语从List
中删除了一系列元素。
list.subList(fromIndex, toIndex).clear();
可以构造类似的惯用法来搜索范围内的元素。
int i = list.subList(fromIndex, toIndex).indexOf(o);
int j = list.subList(fromIndex, toIndex).lastIndexOf(o);
请注意,前面的惯用法返回subList
中找到的元素的索引,而不返回后备List
中的索引。
任何对List
进行操作的多态算法,例如replace
和shuffle
示例,都可以与subList
返回的List
一起使用。
这是一个多态算法,其实现使用subList
来处理牌组中的一手牌。也就是说,它返回一个新的List
(“手”),其中包含从指定的List
(“平台”)的末尾取出的指定数量的元素。手中返回的元素将从牌组中删除。
public static <E> List<E> dealHand(List<E> deck, int n) {
int deckSize = deck.size();
List<E> handView = deck.subList(deckSize - n, deckSize);
List<E> hand = new ArrayList<E>(handView);
handView.clear();
return hand;
}
请注意,此算法从牌组末端移开了手。对于许多常见的List
实现,例如ArrayList
,从列表末尾删除元素的性能明显好于从开头删除元素的性能。
以下是a program,它结合使用dealHand
方法和Collections.shuffle
来从普通的 52 张牌中产生手牌。该程序使用两个命令行参数:(1)要发牌的手数和(2)每手牌的手数。
import java.util.*;
public class Deal {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: Deal hands cards");
return;
}
int numHands = Integer.parseInt(args[0]);
int cardsPerHand = Integer.parseInt(args[1]);
// Make a normal 52-card deck.
String[] suit = new String[] {
"spades", "hearts",
"diamonds", "clubs"
};
String[] rank = new String[] {
"ace", "2", "3", "4",
"5", "6", "7", "8", "9", "10",
"jack", "queen", "king"
};
List<String> deck = new ArrayList<String>();
for (int i = 0; i < suit.length; i++)
for (int j = 0; j < rank.length; j++)
deck.add(rank[j] + " of " + suit[i]);
// Shuffle the deck.
Collections.shuffle(deck);
if (numHands * cardsPerHand > deck.size()) {
System.out.println("Not enough cards.");
return;
}
for (int i = 0; i < numHands; i++)
System.out.println(dealHand(deck, cardsPerHand));
}
public static <E> List<E> dealHand(List<E> deck, int n) {
int deckSize = deck.size();
List<E> handView = deck.subList(deckSize - n, deckSize);
List<E> hand = new ArrayList<E>(handView);
handView.clear();
return hand;
}
}
运行程序将产生如下输出。
% java Deal 4 5
[8 of hearts, jack of spades, 3 of spades, 4 of spades,
king of diamonds]
[4 of diamonds, ace of clubs, 6 of clubs, jack of hearts,
queen of hearts]
[7 of spades, 5 of spades, 2 of diamonds, queen of diamonds,
9 of clubs]
[8 of spades, 6 of diamonds, ace of spades, 3 of hearts,
ace of hearts]
尽管subList
功能非常强大,但是使用它时必须格外小心。如果通过除返回的List
之外的任何其他方式将元素添加到支持List
或从支持List
中删除,则subList
返回的List
的语义将变得不确定。因此,强烈建议您仅将subList
返回的List
用作瞬态对象—对List
进行一次或一系列范围操作。使用subList
实例的时间越 Long,通过直接或通过另一个subList
对象修改后备List
损害它的可能性就越大。请注意,修改子列表的子列表并 continue 使用原始子列表是合法的(尽管不能同时使用)。
List Algorithms
Collections
类中的大多数多态算法专门适用于List
。拥有所有这些算法可轻松处理列表。这是这些算法的摘要,在Algorithms部分有更详细的描述。
-
sort
—使用合并排序算法对List
进行排序,该算法可提供快速,稳定的排序。 (稳定排序是一种不会对相等元素进行重新排序的排序.) -
shuffle
—随机排列List
中的元素。 -
reverse
—反转List
中元素的 Sequences。 -
rotate
—将List
中的所有元素旋转指定的距离。 -
swap
-交换List
中指定位置的元素。 -
replaceAll
—将所有出现的一个指定值替换为另一个。 -
fill
—用指定的值覆盖List
中的每个元素。 -
copy
—将源List
复制到目标List
中。 -
binarySearch
—使用二进制搜索算法搜索有序List
中的元素。 -
indexOfSubList
—返回一个List
的第一个子列表的索引,该索引等于另一个。 -
lastIndexOfSubList
—返回一个List
的最后一个子列表的索引,该索引等于另一个。