11.5 List
有两种类型的List:
①、ArrayList:擅长随机访问元素,但在List中间插入和移除元素较慢。
②、LinkedList:在List中间进行插入和删除操作的代价较低,优化的顺序访问,随机访问较慢,特性集比ArrayList大。
书中的代码就不贴在这里了,然后书中介绍了常用的API,需要的话可以自行去查帮助文档。
有几个方法我没用过或很少用,在这里特别记录一下:
①、List
示例代码:
Integer[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 实例化一个list,并获取其一个子集
List
List
System.out.println("original list:" + list);
System.out.println("original subList:" + subList);
// 子集的索引从0开始
System.out.println("subList get(0):" + subList.get(0));
// 在子集中增、删、改、清空均会影响到父集
System.out.println("----------subList remove----------");
subList.remove(0);
System.out.println("original list:" + list);
System.out.println("subList remove(0):" + subList);
System.out.println("----------subList add----------");
subList.add(10);
System.out.println("original list:" + list);
System.out.println("subList add(10):" + subList);
System.out.println("----------subList set----------");
subList.set(0, 20);
System.out.println("original list:" + list);
System.out.println("subList set(0, 20):" + subList);
System.out.println("----------subList clear----------");
subList.clear();
System.out.println("original list:" + list);
System.out.println("subList clear():" + subList);
运行结果如下:
original list:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
original subList:[2, 3, 4]
subList get(0):2
----------subList remove----------
original list:[0, 1, 3, 4, 5, 6, 7, 8, 9]
subList remove(0):[3, 4]
----------subList add----------
original list:[0, 1, 3, 4, 10, 5, 6, 7, 8, 9]
subList add(10):[3, 4, 10]
----------subList set----------
original list:[0, 1, 20, 4, 10, 5, 6, 7, 8, 9]
subList set(0, 20):[20, 4, 10]
----------subList clear----------
original list:[0, 1, 5, 6, 7, 8, 9]
subList clear():[]
对父集list进行了某些结构性的修改,比如add()、remove()等导致modCount的值改变,则再调用子集subList的某些方法,如:set()、get()、size()等方法会抛出异常java.util.ConcurrentModificationException(子集方法中调用了checkForComodification()方法,该方法会检查父集list的modCount与创建子集subList时的modCount是否相等)。
示例代码:
Integer[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 实例化一个list,并获取其一个子集
List
List
System.out.println("original list:" + list);
System.out.println("original subList:" + subList);
list.remove(0);
// 子集的索引从0开始
System.out.println("subList get(0):" + subList.get(0));
运行结果如下:
original list:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
original subList:[2, 3, 4]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
at java.util.ArrayList$SubList.get(ArrayList.java:1043)
at com.example.test.container.Test.main(Test.java:23)
备注:subList()方法返回的是ArrayList的内部类SubList。其和ArrayList是两个独立的个体,无法强转为ArrayList(我之前好像犯过这个错误!)。
②、public static void shuffle(List> list):使用默认随机源对指定列表进行置换。所有置换发生的可能性都是大致相等的。
public static void shuffle(List> list, Random rnd):使用指定的随机源对指定列表进行置换。所有置换发生的可能性都是相等的,假定随机源是公平的。
这是我在帮助文档中看到的解释,但是我觉得看得不太明白。其实,shuffle()方法就是打乱List中元素的顺序,就好像该单词的英文翻译一样:洗牌。
示例代码:
Random random = new Random(47);
Integer[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 实例化一个list
List
System.out.println("Before shufflig:" + list);
// 指定随机源打乱元素
Collections.shuffle(list, random);
System.out.println("After shufflig:" + list);
运行结果如下:
Before shufflig:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
After shufflig:[3, 5, 2, 0, 7, 6, 1, 4, 9, 8]
③、boolean retainAll(Collection> c):仅在列表中保留指定 collection 中所包含的元素(未包含在指定collection中的所有元素均被移除),即获取两个集合的交集。
代码如下:
Integer[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Integer[] b = { 7, 8, 9, 10, 11, 12, 13, 14 };
// 实例化两个list
List
List
System.out.println("original aList:" + aList);
// 获取aList与bList的交集
aList.retainAll(bList);
System.out.println("after retainAll:" + aList);
运行结果如下:
original aList:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
after retainAll:[7, 8, 9]
练习题5和练习题6略过。
练习题7:创建一个类,然后创建一个用你的类的对象进行过初始化的数组。通过使用subList()方法,创建你的List子集,然后在你的List中移除这个子集。
代码如下:
public class Fish {
private String kind;
public Fish(String kind) {
this.kind = kind;
}
@Override
public String toString() {
return "Fish [kind=" + kind + "]";
}
public static void main(String[] args) {
Fish[] fishes = new Fish[] { new Fish("鲤鱼"), new Fish("飞鱼"), new Fish("金枪鱼"), new Fish("鲨鱼"), new Fish("大麻哈鱼") };
// 实例化一个List,且获取其子集
List
List
// 输出父集和子集
System.out.println("fishList:" + fishList);
System.out.println("subFishList:" + subFishList);
// 在父集中移除子集
System.out.println("----------fishList removeAll subFishList----------");
fishList.removeAll(subFishList);
System.out.println("fishList:" + fishList);
}
}
运行结果如下:
fishList:[Fish [kind=鲤鱼], Fish [kind=飞鱼], Fish [kind=金枪鱼], Fish [kind=鲨鱼], Fish [kind=大麻哈鱼]]
subFishList:[Fish [kind=飞鱼], Fish [kind=金枪鱼]]
----------fishList removeAll subFishList----------
fishList:[Fish [kind=鲤鱼], Fish [kind=鲨鱼], Fish [kind=大麻哈鱼]]
11.6 迭代器
迭代器是一种设计模式,而Collection里的迭代器是一个对象,它被用于遍历并选择Collection里的元素。此外,迭代器通常被称为轻量级对象,而且Iterator只能单向移动。
使用Iterator过程如下:
①、用iterator()方法去创建一个迭代器实例。
②、用hasNext()判断是否有下一个元素。
③、用next()方法来获取下一个元素。
④、可以使用remove()方法删除该元素(可选操作)。
迭代器能够将遍历序列的操作与序列的底层结构分离,它统一了对容器的访问方式。
练习题8:修改练习题1,以便调用hop()时使用Iterator遍历List。
代码如下:
public class Gerbil {
private int gerbilNumber;
public Gerbil(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public void hop() {
System.out.println("第" + gerbilNumber + "只沙鼠,正在跳跃");
}
public static void main(String[] args) {
List
for (int i = 1; i <= 5; i++) {
Gerbil gerbil = new Gerbil(i);
gerbils.add(gerbil);
}
Gerbil gerbil;
Iterator
while(it.hasNext()){
gerbil = it.next();
gerbil.hop();
}
}
}
练习题9:修改innerclasses/Sequence.java,使得在Sequence中,用Iterator取代Selector。
代码如下:
public class Sequence
private List
public void add(T t) {
container.add(t);
}
public Iterator
return container.iterator();
}
public static void main(String[] args) {
Sequence
for (int i = 0; i < 10; i++) {
Employee emp = new Employee(i, "compony" + i);
container.add(emp);
}
Employee e;
Iterator
while (it.hasNext()) {
e = it.next();
System.out.println(e);
}
}
}
练习题10:修改第8章中的练习9,使其使用一个ArrayList来存放Rodents,并使用一个Iterator来访问Rodent序列。→ 略过,同练习题8、9类似。
练习题11:写一个方法,使用Iterator遍历Collection,并打印容器中每个对象的toString()。填充各种类型的Collection,然后对其使用此方法。→ 略过,同书中示例代码一样。
11.6.1 ListIterator
ListIterator是Iterator的子类型,它只能用于各种List类的访问。相较于Iterator,它可以双向移动,而且它可以获得当前位置的前一个和后一个元素的索引,也可以用set()方法替换它访问过的最后一个元素。有两个创建它的方法:
①、ListIterator
②、ListIterator
练习题12:创建并组装一个List
代码如下:
public static void main(String[] args) {
Integer[] a = new Integer[] { 34, 21, 7, 87, 45 };
// 实例化正向和反向的List
List
List
// 获取正向List的ListIterator,并让其指向最后一个元素的索引
ListIterator
// 依次获取前面的元素,放到反向的List中
while (lit.hasPrevious()) {
backList.add(lit.previous());
}
System.out.println("正向的List:" + frontList);
System.out.println("反向的List:" + backList);
}
运行结果如下:
正向的List:[34, 21, 7, 87, 45]
反向的List:[45, 87, 7, 21, 34]
11.7 LinkedList
LinkedList实现了基本的List接口,它还添加了使其用作栈、队列或双端队列的方法。
书中代码只是演示了添加(add()、addFirst()、addLast()、offer()等)、移除(remove()、removeFirst()、poll()等)、获取(get()、getFirst()、peek()等)API的使用,这里就不贴出来了。
练习题13:在innerclasses/GreenhouseController.java示例中,Controller类使用的是ArrayList,修改代码,用LinkedList替换之,并使用Iterator来循环遍历事件集。
代码如下:
public class Controller {
// 事件容器
private List
/**
* 添加事件
*
* @param e
*/
public void addEvent(Event e) {
eventList.add(e);
}
public void run() {
while (eventList.size() > 0) {
// 由于会对LinkedList产生结构性修改(如Bell事件触发的action(),会将
// 自己添加到LinkedList里等),因此需要拷贝一个副本进行操作。
List
Event e;
Iterator
while (it.hasNext()) {
e = it.next();
if (e.ready()) {
System.out.println(e);
e.action();
it.remove();
}
}
}
}
}
练习题14:创建一个空的LinkedList
代码如下:
方法一:
public static void main(String[] args) {
List
for (int i = 0; i < 10; i++) {
ListIterator
lit.add(i);
}
System.out.println(list);
}
方法二:
public static void main(String[] args) {
List
ListIterator
for (int i = 0; i < 10; i++) {
lit.add(i);
if (i % 2 == 0)
lit.previous();
}
System.out.println(list);
}
运行结果如下:
[1, 3, 5, 7, 9, 8, 6, 4, 2, 0]
11.8 Stack
栈通常是指"后进先出"(LIFO)的容器。有时栈也被称为叠加栈,因为最后"压入"栈的元素,第一个"弹出"栈。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。
将LinkedList作为栈使用,书中演示代码如下:
public class Stack
private LinkedList
public void push(T t) {
storage.addFirst(t);
}
public T peek() {
return storage.getFirst();
}
public T pop() {
return storage.removeFirst();
}
public boolean empty() {
return storage.isEmpty();
}
public String toString() {
return storage.toString();
}
}
练习题15:栈在编程语言中经常用来对表达式求值。请使用net.mindview.util.Stack对下面表达式求值,其中“+”表示“将后面的字母压进栈”,而“-”表示“弹出栈顶字母并打印它”:“+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+l+e+s---”
代码如下:
public static void main(String[] args) {
String s = "+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+l+e+s---";
Stack
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '+') {
stack.push(s.charAt(++i));
} else if (c == '-') {
System.out.print(stack.pop());
}
}
}
11.9 Set
Set不保存重复元素,它常被用于测试某个对象是否在某个Set中。因此,查找是其最重要的操作,我们可以选择HashSet,它专门对快速查找进行了优化。
Set具有与Collection完全一样的接口,它就是Collection,只是行为不同(继承与多态的典型应用:表现不同的行为)。它是基于对象的"值"来确定是否是其成员的。
TreeSet将元素存储在红─黑树中,而且会对元素排序;HashSet使用的是散列函数;LinkedHashSet因为查询速度的原因也使用了散列,但是它使用链表来维护元素的插入顺序。
练习题16:创建一个元音字母Set。对UniqueWords.java操作,计数并显示在每一个输入单词中的元音字母数量,并显示输入文件中的所有元音字母的数量总和。
代码如下:
package com.example.test.container;
import java.util.Set;
import java.util.TreeSet;
import net.mindview.util.TextFile;
public class UniqueWords {
public static void main(String[] args) {
Set
System.out.println(words);
}
}
public class UniqueWordsVowels {
public static void main(String[] args) {
int totalVowel = 0;
// 元音集合
Set
Collections.addAll(vowels, new Character[] { 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' });
// UniqueWords.java的单词集合
Map
for (String word : new TextFile("src/com/example/test/container/UniqueWords.java", "\\W+")) {
int wordVowel = 0;
if (searched.containsKey(word)) {
wordVowel = searched.get(word);
} else {
for (char c : word.toCharArray()) {
if (vowels.contains(c)) {
wordVowel++;
}
}
searched.put(word, wordVowel);
}
totalVowel += wordVowel;
System.out.println("单词:" + word + "的元音个数为:" + wordVowel);
}
System.out.println("元音的个数为:" + totalVowel);
}
}
输出结果:
单词:package的元音个数为:3
单词:com的元音个数为:1
单词:example的元音个数为:3
单词:test的元音个数为:1
单词:container的元音个数为:4
单词:import的元音个数为:2
单词:java的元音个数为:2
单词:util的元音个数为:2
单词:Set的元音个数为:1
单词:import的元音个数为:2
单词:java的元音个数为:2
单词:util的元音个数为:2
单词:TreeSet的元音个数为:3
单词:import的元音个数为:2
单词:net的元音个数为:1
单词:mindview的元音个数为:3
单词:util的元音个数为:2
单词:TextFile的元音个数为:3
单词:public的元音个数为:2
单词:class的元音个数为:1
单词:UniqueWords的元音个数为:5
单词:public的元音个数为:2
单词:static的元音个数为:2
单词:void的元音个数为:2
单词:main的元音个数为:2
单词:String的元音个数为:1
单词:args的元音个数为:1
单词:Set的元音个数为:1
单词:String的元音个数为:1
单词:words的元音个数为:1
单词:new的元音个数为:1
单词:TreeSet的元音个数为:3
单词:String的元音个数为:1
单词:new的元音个数为:1
单词:TextFile的元音个数为:3
单词:src的元音个数为:0
单词:com的元音个数为:1
单词:example的元音个数为:3
单词:test的元音个数为:1
单词:container的元音个数为:4
单词:SetOperations的元音个数为:6
单词:java的元音个数为:2
单词:W的元音个数为:0
单词:System的元音个数为:1
单词:out的元音个数为:2
单词:println的元音个数为:1
单词:words的元音个数为:1
元音的个数为:91