# 列表接口
List 是有序 Collection(有时称为序列)。列表可能包含重复的元素。除了继承 Collection 的操作之外,List 接口还包括以下操作:
Positional access
基于它们在列表中的数字位置来操作元素。这包括的方法,例如 get, set,add,addAll,和 remove。
Search
在列表中搜索指定的对象,并返回其数值位置。搜索方法包括 indexOf 和 lastIndexOf。
Iteration
扩展 Iterator 语义以利用列表的顺序性质。该 listIterator 方法提供了这种行为。
Range-view sublist 方法对列表执行任意范围操作。
Java 平台包含两个通用 List 实现。 ArrayList 通常是性能更好的实现, LinkedList 在某些情况下提供更好的性能。
# 集合操作
remove 操作总是删除列表中的第一个元素,在 add 和 addAll 操作总是追加新的元素(S)到列表末尾。
下面将一个列表追加到另外一个列表中
list1.addAll(list2);
像 Set 接口一样,List 加强对 equals 和 hashCode 方法的要求,使得两个 List 对象可以比较逻辑相等而不考虑它们的实现类。 如果两个对象包含相同顺序的相同元素,则它们相等。
# 位置访问和搜索操作
基本的位置操作是 get、set、add 和 remove。(set 和 remove 操作返回被覆盖或删除的旧值)。 其他操作(indexOf 和 lastIndexOf)返回列表中指定元素的第一个或最后一个索引。
addAll 操作 ,添加指定 collection 中的所有元素到此列表的结尾,顺序是指定 collection 的迭代器返回这些元素的顺序(可选操作)。
这里有一个小方法来交换两个索引的值
@Test
public void test() {
List<String> s1 = new ArrayList<>();
s1.add("2");
s1.add("3");
s1.add("1");
System.out.println(s1); //[2, 3, 1]
swap(s1, 0, 1);
System.out.println(s1); //[3, 2, 1]
}
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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
有一个很大的区别,这是一个多态性算法;它交换任何 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));
}
2
3
4
此算法包含在 Java 平台的 Collections 类中,使用指定的随机源随机置换指定的列表。 它有点微妙:它从底部向上运行列表,反复交换随机选择的元素到当前位置。 与大多数天真洗牌的尝试,它是公平的(所有的排列与平等的可能性发生,假设随机性的无偏来源)和快速(要求完全互换)。
下面使用随机算法在参数列表中打印单词:
String[] arrs = {"1", "2", "3", "4", "5"};
List<String> list = new ArrayList<>();
for (String arr : arrs) {
list.add(arr);
}
Collections.shuffle(list, new Random());
System.out.println(list);
2
3
4
5
6
7
实际上,这个程序还可以这样做
String[] arrs = {"1", "2", "3", "4", "5"};
// java.util.List<T> 该方法返回的是一个List,而内部实现确实一个 同名的内部类 ArrayList
// 这种方法返回的list 不支持 add 和 remove 操作,且数组列表固定不可变。
List<String> list = Arrays.asList(arrs);
Collections.shuffle(list); // 使用默认随机源
System.out.println(list);
2
3
4
5
6
!说实话的话,我一直不明白 asList
返回的 ArrayList
为什么不能进行更改。现在才知道。
# 迭代器
Iterator 由 List 的 iterator 操作返回正确的顺序列表元素的迭代器。 List 还提供了个更丰富的迭代器,ListIterator,它允许您在任一方向上遍历列表, 在迭代期间修改列表,并获取迭代器的当前位置。
ListIterator 从 Iterator(hasNext,next和remove)继承的三个方法在两个接口中执行完全相同的事情。 hasPrevious 和 previous 操作类似 hasNext 和 next。前面的操作引用(隐式)游标之前的元素, 而后者引用游标之后的元素。该 previous 操作光标向后移动,而 next 向前移动了。
这里是一个标准的迭代语句
for( ListIterator<String> it = list.listIterator(list.size());it.hasPrevious();){
String previous = it.previous();
System.out.println(previous);
}
2
3
4
注意参数,无参数的 listIterator 返回的 Iterator 是以列表起始位置开始的迭代器。 而有参的是返回以指定位置开始的迭代器。
有一个 4 个元素的列表,光标位置有 5 个可能的位置:
element(0) | element(1) | element(2) | element(3)
index 0 | 1 | 2 | 3 | 4
2
换句话说:由于有参的能自定义位置,所以,底层 previous() 的时候是 先将游标位置 -1, 而无参的游标位置始终都是 0,所以先取元素再 + 1。 所以就存在了图上的 4 元素存在 5 种可能的游标位置
previous 和 next 可以混合使用,但是需要小心别让自己获取到同一个元素。 nextIndex 和 previousIndex 返回下一个,上一个游标的位置,和 next 与 previous 的计算方式类似。
nextIndex 返回的数字总是 大于 previousIndex ,也就会产生两个边界,-1 和 list.size. 下面是 List.indexOf 的实现原理:
@Test
public void test() {
String[] arrs = {"1", "2", "3", "4", "5"};
List<String> list = Arrays.asList(arrs);
String target = "3";
System.out.println(indexOf(list,target)); // 3
}
public int indexOf(List<String> list, String target) {
for (ListIterator<String> it = list.listIterator(); it.hasNext(); ) {
// next()之后,nextIndex 就已经是下一个元素的值了
// 所以这里使用上一个元素的位置,正好得到匹配元素的位置
if (target == null ? it.next() == null : target.equals(it.next()))
return it.previousIndex();
}
return -1;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Iterator,remove() 从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。 ListIterator 附加两个操作:add 和 set
set(E e) 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
public void test() { String[] arrs = {"1", "2", "3", "4", "5"}; List<String> list = new ArrayList<>(Arrays.asList(arrs)); replace(list,"3","6"); System.out.println(list); //[1, 2, 6, 4, 5] } 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); }
1
2
3
4
5
6
7
8
9
10
11add(E e) 将指定的元素插入列表(可选操作)。
下面的多态性算法说明了此方法,将指定值替换成指定列表中的值
@Test public void test() { String[] arrs = {"1", "2", "3", "4", "5"}; String[] arrs2 = {"6", "7",}; List<String> list = new ArrayList<>(Arrays.asList(arrs)); replace(list, "3", new ArrayList<>(Arrays.asList(arrs2))); System.out.println(list); // [1, 2, 6, 7, 4, 5] } 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); // 是添加到当游标位置,操作指定位置的值 } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 范围视图操作
在 range-view 操作中,subList(int fromIndex, int toIndex) 返回 List 中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
for (int i = fromIndex; i < toIndex; i++) {
...
}
2
3
返回的是一个 SubList
对象,没有新建一个 List,而是使用源 list 中的容器,自己只不过维护了这部分视图的指针偏移量,和个数,
所以在 sublist
中操作其实也是在操作源 list;
如清空这部分视图,源 list 跟着改变
String[] arrs = {"1", "2", "3", "4", "5"};
List<String> list = new ArrayList<>(Arrays.asList(arrs));
List<String> list1 = list.subList(0, 2);
list1.clear();
System.out.println(list1); //[]
System.out.println(list); // [3, 4, 5]
2
3
4
5
6
还可以用来做范围搜索
String[] arrs = {"1", "2", "3", "4", "5"};
List<String> list = new ArrayList<>(Arrays.asList(arrs));
int i = list.subList(1, 4).indexOf("2");
int j = list.subList(1, 4).lastIndexOf("2");
System.out.println(i + "," + j); //0,0
2
3
4
5
要注意的是,返回的索引 是 subList 视图中的索引,而不是源列表中的
这里是一个多态的算法,从列表指定处移除 n 个元素,返回被移除的元素
@Test
public void test() {
String[] arrs = {"1", "2", "3", "4", "5"};
List<String> list = new ArrayList<>(Arrays.asList(arrs));
System.out.println(dealHand(list,3)); //[3, 4, 5]
System.out.println(list); //[1, 2]
}
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意,该算法从牌组的末端去除手牌。对于许多常见的 List 实现,例如 ArrayList,从列表的末尾移除元素的性能明显好于从开始移除元素的性能。
使用这个算法来实现 52 张牌的发牌
public class Deal {
public static void main(String[] args) {
int numHands = 4; // 人数
int cardsPerHand = 5; // 发牌数量
// 生成52张牌
String[] suit = new String[]{
"黑桃", "红心",
"方块", "梅花"
};
String[] rank = new String[]{
"A", "2", "3", "4",
"5", "6", "7", "8", "9", "10",
"J", "Q", "K"
};
List<String> deck = new ArrayList<String>();
for (int i = 0; i < suit.length; i++)
for (int j = 0; j < rank.length; j++)
deck.add(suit[i] + rank[j]);
// 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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
输出结果
[方块K, 黑桃3, 梅花4, 黑桃7, 黑桃K]
[梅花Q, 梅花5, 梅花10, 红心6, 梅花J]
[红心10, 梅花K, 黑桃5, 红心A, 红心4]
[黑桃9, 黑桃10, 黑桃Q, 红心5, 方块Q]
2
3
4
虽然 subList 操作非常强大,但是使用必须小心,因为是共享,但是返回的对象不是同一个,维护困难,建议只是用来临时操作。
# 列表算法
Collections 类中的大多数多态性算法特别适用于 List。拥有所有这些算法在您的处置使它很容易操纵列表。 这里是这些算法的总结,这些算法在“ 算法” 部分中有更详细的描述 。
- sort - List 使用归并排序算法,它提供了一种快速、稳定的排序。
- shuffle- 随机排列 List 中元素的顺序。
- reverse- 反转指定列表中元素的顺序。
- rotate- 根据指定的距离轮换指定列表中的元素。
- swap- 在指定列表的指定位置处交换元素。 。
- replaceAll - 使用另一个值替换列表中出现的所有某一指定值。
- fill- 使用指定元素替换指定列表中的所有元素。
- copy- 将源 List 复制到目标 List。
- binarySearch- 使用二分搜索法搜索指定列表,以获得指定对象
- indexOfSubList- 返回指定源列表中第一次出现指定目标列表的起始位置。
- lastIndexOfSubList- 返回指定源列表中最后一次出现指定目标列表的起始位置。