Java编程思想第十一章"持有对象"的11.5~11.9小节的笔记和练习题

    11.5 List
    有两种类型的List:
    ①、ArrayList:擅长随机访问元素,但在List中间插入和移除元素较慢。
    ②、LinkedList:在List中间进行插入和删除操作的代价较低,优化的顺序访问,随机访问较慢,特性集比ArrayList大。
    书中的代码就不贴在这里了,然后书中介绍了常用的API,需要的话可以自行去查帮助文档。
    有几个方法我没用过或很少用,在这里特别记录一下:
    ①、List subList(int fromIndex, int toIndex):返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。(如果 fromIndex 和 toIndex 相等,则返回的列表为空)。返回的列表由此列表支持,因此返回列表中的非结构性更改将反映在此列表中,反之亦然。
    示例代码:
Integer[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// 实例化一个list,并获取其一个子集
List list = new ArrayList(Arrays.asList(a));
List subList = list.subList(2, 5); // [2,5)
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 = new ArrayList(Arrays.asList(a));
List subList = list.subList(2, 5); // [2,5)
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 list = new ArrayList(Arrays.asList(a));
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 aList = new ArrayList(Arrays.asList(a));
List bList = new ArrayList(Arrays.asList(b));
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 fishList = new ArrayList(Arrays.asList(fishes));
        List subFishList = fishList.subList(1, 3);
        // 输出父集和子集
        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 gerbils = new ArrayList();
        for (int i = 1; i <= 5; i++) {
            Gerbil gerbil = new Gerbil(i);
            gerbils.add(gerbil);
        }

        Gerbil gerbil;
        Iterator it = gerbils.iterator();
        while(it.hasNext()){
            gerbil = it.next();
            gerbil.hop();
        }
    }
}

    练习题9:修改innerclasses/Sequence.java,使得在Sequence中,用Iterator取代Selector。
代码如下:
public class Sequence {

    private List container = new ArrayList();

    public void add(T t) {
        container.add(t);
    }

    public Iterator getIterator() {
        return container.iterator();
    }

    public static void main(String[] args) {
        Sequence container = new Sequence<>();
        for (int i = 0; i < 10; i++) {
            Employee emp = new Employee(i, "compony" + i);
            container.add(emp);
        }

        Employee e;
        Iterator it = container.getIterator();
        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():产生一个指向List开始处的ListIterator。
    ②、ListIterator listIterator(int index):产生一个指向List索引index处的ListIterator。

    练习题12:创建并组装一个List,然后创建第二个具有相同尺寸的List,并使用ListIterator读取第一个List中的元素,然后再将它们以反序插入到第二个列表中。
    代码如下:
public static void main(String[] args) {
    Integer[] a = new Integer[] { 34, 21, 7, 87, 45 };
    // 实例化正向和反向的List
    List frontList = new ArrayList(Arrays.asList(a));
    List backList = new ArrayList(frontList.size());
    // 获取正向List的ListIterator,并让其指向最后一个元素的索引
    ListIterator lit = frontList.listIterator(frontList.size());
    // 依次获取前面的元素,放到反向的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 eventList = new LinkedList();

    /**
     * 添加事件
     * 
     * @param e
     */
    public void addEvent(Event e) {
        eventList.add(e);
    }

    public void run() {
        while (eventList.size() > 0) {
            // 由于会对LinkedList产生结构性修改(如Bell事件触发的action(),会将
            // 自己添加到LinkedList里等),因此需要拷贝一个副本进行操作。
            List copyEventList = new LinkedList(eventList);
            Event e;
            Iterator it = copyEventList.iterator();
            while (it.hasNext()) {
                e = it.next();
                if (e.ready()) {
                    System.out.println(e);
                    e.action();
                    it.remove();
                }
            }
        }
    }
}

    练习题14:创建一个空的LinkedList,通过使用ListIterator,将若干个Integer插入这个List中,插入时,总是将它们插入到List的中间。
    代码如下:
    方法一:
public static void main(String[] args) {
    List list = new LinkedList();
    for (int i = 0; i < 10; i++) {
        ListIterator lit = list.listIterator(list.size() / 2);
        lit.add(i);
    }

    System.out.println(list);
}

    方法二:
public static void main(String[] args) {
    List list = new LinkedList();
    ListIterator lit = 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 storage = new 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 stack = new 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 words = new TreeSet(new TextFile("src/com/example/test/container/SetOperations.java", "\\W+"));
        System.out.println(words);
    }
}

public class UniqueWordsVowels {

    public static void main(String[] args) {
        int totalVowel = 0;
        // 元音集合
        Set vowels = new HashSet();
        Collections.addAll(vowels, new Character[] { 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U' });
        // UniqueWords.java的单词集合
        Map searched = new HashMap();
        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

你可能感兴趣的:(Java)