List是一个有序的Collection(有时称为序列),列表可能包含重复元素,除了从Collection
继承的操做以外,List
接口还包括如下操做:html
get
、set
、add
、addAll
和remove
等方法。indexOf
和lastIndexOf
。Iterator
语义以利用列表的顺序性,listIterator
方法提供此行为。sublist
方法对列表执行任意范围操做。Java平台包含两个通用的List
实现,ArrayList,一般是性能更好的实现,而LinkedList在某些状况下提供更好的性能。java
假设你已经熟悉它们,那么从Collection
继承的操做均可以完成你指望它们作的事情,若是你不熟悉Collection
,如今是阅读Collection接口部分的好时机,remove
操做始终从列表中删除指定元素的第一个匹配项,add
和addAll
操做始终将新元素附加到列表的末尾,所以,如下语法将一个列表链接到另外一个列表。git
list1.addAll(list2);
这是这个语法的非破坏性形式,它产生第三个List
,其中包含附加到第一个列表的第二个列表。github
List<Type> list3 = new ArrayList<Type>(list1); list3.addAll(list2);
请注意,此语法在其非破坏性形式中利用了ArrayList
的标准转换构造函数。算法
这是一个将一些名称聚合到List
中的示例(JDK 8及更高版本):小程序
List<String> list = people.stream() .map(Person::getName) .collect(Collectors.toList());
与Set接口同样,List
强化了对equals
和hashCode
方法的需求,所以能够比较两个List
对象的逻辑相等性,而不考虑它们的实现类,若是两个List
对象包含相同顺序的相同元素,则它们是相等的。segmentfault
基础的位置访问操做是get
、set
、add
和remove
(set
和remove
操做返回被覆盖或删除的旧值),其余操做(indexOf
和lastIndexOf
)返回列表中指定元素的第一个或最后一个索引。api
addAll
操做从指定位置开始插入指定Collection
的全部元素,元素按指定Collection
的迭代器返回的顺序插入,此调用是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类中,使用指定的随机源随机置换指定的列表,这有点微妙:它从底部向上运行列表,反复将随机选择的元素交换到当前位置。不像大多数天真的洗牌尝试,这是公平的(假设一个公平的随机源,全部排列都有相同的可能性)和快速(须要彻底list.size()-1
交换),如下程序使用此算法以随机顺序打印其参数列表中的单词。
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
不是通用List
实现,由于它没有实现(可选)add
和remove
操做:数组不可调整大小。利用Arrays.asList
并调用shuffle
的库版本(使用默认的随机源),你将获得如下微小程序,其行为与前一个程序相同。
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); } }
正如你所指望的那样,List
的iterator
操做返回的Iterator
以适当的顺序返回列表的元素,List
还提供了一个更丰富的迭代器,称为ListIterator
,它容许你在任一方向遍历列表、在迭代期间修改列表、并获取迭代器的当前位置。
ListIterator
从Iterator
继承的三个方法(hasNext
、next
和remove
)在两个接口中彻底相同,hasPrevious
和previous
操做和hasNext
和next
的很类似,前一个操做引用(隐式)游标以前的元素,然后者引用游标以后的元素,previous
操做向后移动光标,而next
向前移动光标。
这是在列表中向后迭代的标准语法。
for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) { Type t = it.previous(); ... }
请注意前面的语法中listIterator
的参数,List
接口有两种形式的listIterator
方法,不带参数的形式返回位于列表开头的ListIterator
,带有int
参数的形式返回一个位于指定索引处的ListIterator
。索引引用初始调用next
返回的元素,对previous
的初始调用将返回索引为index-1
的元素,在长度为n
的列表中,index
有n+1
个有效值,从0
到n
(包括n
)。
直观地说,游标老是在两个元素之间 — 一个将经过调用previous
返回,一个将经过调用next
返回。n+1
个有效索引值对应于元素之间的n+1
个间隙,从第一个元素以前的间隙到最后一个元素以后的间隙,下图显示了包含四个元素的列表中的五个可能的游标位置。
对next
和previous
的调用能够混合使用,可是必须当心一点,对previous
的第一次调用返回与对next
的最后一次调用相同的元素,相似地,在对previous
进行一系列调用以后,对next
的第一次调用与对previous
的最后一次调用返回相同的元素。
nextIndex
方法返回后续调用next
返回的元素的索引,而且previousIndex
返回后续调用previous
返回的元素的索引,这些调用一般用于报告找到某些内容的位置或记录ListIterator
的位置,以即可以建立具备相同位置的另外一个ListIterator
。
一样也不足为奇的是,nextIndex
返回的数字老是大于previousIndex
返回的数字,这意味着两种边界状况的行为:(1)当光标位于初始元素以前时对previousIndex
的调用返回-1
,当光标位于最后一个元素以后时调用nextIndex
返回list.size()
。为了使全部这些具体化,如下是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
接口提供remove
操做以从Collection
中删除next
返回的最后一个元素,对于ListIterator
,此操做将删除next
或previous
返回的最后一个元素。ListIterator
接口提供了两个额外的操做来修改列表 — 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); } } }
范围视图操做subList(int fromIndex,int toIndex)
返回此列表部分的List
视图,其索引范围从fromIndex
(包括)到toIndex
(不包括),这个半开放范围反映了典型的for循环。
for (int i = fromIndex; i < toIndex; i++) { ... }
正如术语视图所暗示的那样,返回的List
由调用了subList
的List
进行备份,所以前者中的更改将反映在后者中。
此方法消除了对显式范围操做的须要(对于数组一般存在的排序),任何指望List
的操做均可以经过传递subList
视图而不是整个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
上操做的任何多态算法(例如replace
和shuffle
示例)都与subList
返回的List
一块儿使用。
这是一个多态算法,其实现使用subList
来处理来自牌组的牌,也就是说,它返回一个新的List
(“hand”),它包含从指定List
(“deck”)末尾获取的指定数量的元素,手中返回的元素将从牌组中移除。
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
,从列表末尾删除元素的性能明显优于从头开始删除元素的性能。
如下是一个程序,它将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
添加或删除元素,则subList
返回的List
的语义将变为undefined
。所以,强烈建议你仅将subList
返回的List
用做临时对象 — 在支持List
上执行一个或一系列范围操做,使用subList
实例的时间越长,经过直接修改支持List
或经过另外一个subList
对象来破坏它的可能性就越大,请注意,修改子列表的子列表并继续使用原始子列表(尽管不是并发)是合法的。
Collections
类中的大多数多态算法专门应用于List
,拥有全部这些算法能够很容易地操做列表,如下是这些算法的摘要,这些算法在“算法”部分中有更详细的描述。
sort
— 使用合并排序算法对List
进行排序,该算法提供快速、稳定的排序(稳定排序是不从新排序相同元素的排序)。shuffle
— 随机置换List
中的元素。reverse
— 反转List
中元素的顺序。rotate
— 将List
中的全部元素旋转指定的距离。swap
— 交换列表中指定位置的元素。replaceAll
— 将全部出现的一个指定值替换为另外一个。fill
— 用指定的值覆盖List
中的每一个元素。copy
— 将源列表复制到目标列表。binarySearch
— 使用二进制搜索算法搜索有序List
中的元素。indexOfSubList
— 返回一个List
的第一个子列表的索引,该列表等于另外一个。lastIndexOfSubList
— 返回一个List
的最后一个子列表的索引,该列表等于另外一个。