Thinking in Java读书笔记(三)

第十一章 持有对象

  1. Java容器类库
    Java容器类库的用途是“保存对象”,这里划分两个概念:
    (1)Collection
      一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
    (2)Map
      一组成对的“键值对”对象,允许你使用键来查找值。
    Thinking in Java读书笔记(三)
    注:淡绿色表示接口,红色表示常用类。

  2. 泛型和类型安全容器
    使用泛型可以在编译器防止将错误类型的对象放置到容器中。
    使用方法是在容器后用尖括号括起类型参数(可以有多个),它指定了这个容器实例可以保存的类型,如ArrayList<Type>。并且这个类型可以向上转型。

  3. Collection添加一组元素:
    (1) Arrays.asList():接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。
    (2) Collections.addAll():接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,将元素添加到Collection中。
    (3) Collection.addAll():将一个Collection对象全部添加进去。只能接受Collection对象作为参数,不如前两种使用可变参数列表的灵活。

    import java.util.*;
    
    public class AddingGroups {
    	public static void main(String[] args) {
    		Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
    		Integer[] moreInts = {6, 7, 8, 9, 10};
    		collection.addAll(Arrays.asList(moreInts));
    		//Run significantly faster, but you can't construct a Collection this way:
    		Collections.addAll(collection, 11, 12, 13, 14, 15);
    		Collections.addAll(collection, moreInts);
    		//Produces a list "backed by" an array:
    		List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
    		list.set(1, 99); //OK -- modify an element
    		//list.add(21); //Running error because the underlying array cannot be resized.
    	}
    }
    

    Collection构造器可以接受另一个Collection,用它来将自身初始化,因此你可以使用Arrays.List()来为这个构造器产生输入。但是,Collection.addAll()方法运行起来要快得多,而且构建一个不包含元素的Collection,然后调用Collections.addAll()这种方式很方便,所以这才是首选办法。

  4. 容器的打印
    数组需要借助Arrays.toString()来打印表示,但打印容器无需任何帮助:

    import java.util.*;
    
    public class PrintingContainers {
    	static Collection fill(Collection<String> collection) {
    		collection.add("rat");
    		collection.add("cat");
    		collection.add("dog");
    		collection.add("dog");
    		return collection;
    	}
    	static Map fill(Map<String,String> map) {
    		map.put("rat", "Fuzzy");
    		map.put("cat", "Rags");
    		map.put("dog", "Bosco");
    		map.put("dog", "Spot");
    		return map;
    	}
    	public static void main(String[] args) {
    		System.out.println(fill(new ArrayList<String>()));
    		System.out.println(fill(new LinkedList<String>()));
    		System.out.println(fill(new HashSet<String>()));
    		System.out.println(fill(new TreeSet<String>()));
    		System.out.println(fill(new LinkedHashSet<String>()));
    		System.out.println(fill(new HashMap<String,String>()));
    		System.out.println(fill(new TreeMap<String,String>()));
    		System.out.println(fill(new LinkedHashMap<String,String>()));
    	}
    }
    /*Output:
    [rat, cat, dog, dog]
    [rat, cat, dog, dog]
    [rat, cat, dog]
    [cat, dog, rat]
    [rat, cat, dog]
    {rat=Fuzzy, cat=Rags, dog=Spot}
    {cat=Rags, dog=Spot, rat=Fuzzy}
    {rat=Fuzzy, cat=Rags, dog=Spot}
    */
    

    这里可以看出Collection和Map的区别在于每个“槽”保存的元素个数。Collection每个槽只能保存一个元素。而Map在每个槽内保存了两个对象,即键和与之相关联的值。Collection打印出来的内容用方括号括住,每个元素由逗号分隔。Map则用大括号括住,键与值由等号联系(键在等号左边,值在右边)。
    另外,该例子可以看出一些容器的特性:

    类型 特性
    ArrayList, LinkedList 插入顺序和输出顺序一致,可以重复
    HashSet, TreeSet 插入顺序和输出顺序不一致,不重复
    LinkedHashSet 插入顺序和输出顺序一致,不重复
    HashMap, TreeMap, LinkedHashMap 键值对存储,键不重复
  5. List
    List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上加入了大量的方法,使得可以在List中间可以插入和移除元素。有两种类型的List:
    (1) 基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时比较慢。
    (2) LinkedList,它通过代价较低的在List中间进行得插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
    与数组不同,List允许在它被创建之后添加元素、移除元素、或者自我调整尺寸,即一个可修改的序列。

    方法 功能
    contains(Object obj) 确定某个对象是否在列表中
    remove(int index) , remove(Object obj) 移除一个对象(删除没有的对象返回false)
    indexOf(Object obj) 返回该对象在List中所处位置的索引编号(找不到就返回-1)
    retainAll(Collection c) 返回交集
    removeAll(Collection c) 从列表中移除指定collection中包含的其所有元素
    set(int index, E element) 设置下标为index的元素为element
    subList(int from_index, int to_index) 截取下标从from_index到to_index的列表
    addALL(int index, Collection c) 在下标index处插入指定collection
    toArray() 转变为具有合适尺寸的数组
    add(int index, E element) 在下标index处添加element
    get(int index) 获取下标为index的值
  6. 迭代器
    迭代器是一个对象,它的工作是遍历并选择对象中的对象。
    Java的Iterator只能单向移动,这个Iterator只能用来:
    (1) 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
    (2) 使用next()获得序列中的下一个元素。
    (3) 使用hasNext()检查序列中是否还有元素。
    (4) 使用remove()将迭代器新近返回的元素删除。
    迭代器统一了对容器的访问方式。

    ListIterator
    更加强大的Iterator子类型,只能用于各种List类的访问。ListIterator可以双向移动。
    (1) 使用方法listIterator()要求容器返回一个指向List开始处的ListIterator。使用方法listIterator(n)要求容器返回一个一开始指向List索引为n的元素处的ListIterator。
    (2) 使用next()获得序列中的下一个元素。使用previous()获得序列中的上一个元素。
    (3) 使用hasNext()检查序列中是否还有元素。使用hasPrevious()检查序列是否有上一个元素。
    (4) 使用remove()将迭代器新近返回的元素删除。
    (5) 使用set()修改迭代器新近返回的元素。

  7. LinkedList
    LinkedList也实现List接口,插入和删除操作比较高效,但在随机访问操作方面逊色一筹。LinkedList还添加了可以使其使用栈、队列或双端队列的语法。

    方法 功能
    getFirst(), element() 返回列表的第一个元素,而并不移除它,如果List为空,抛出NoSuchElementException
    peek() 功能同上,列表为空时返回null
    removeFirst(), remove() 移除并返回列表的第一个元素,列表为空时返回NoSuchElementException
    poll() 功能同上,列表为空时返回null
    add(), addFirst(), addLast() 将某个元素插入到列表的尾端
    removeLast() 移除并返回列表的最后一个元素
  8. Set
    Set不保存重复的元素。查找是Set中最重要的操作,其中HashSet对快速查找进行了优化。最常见操作之一就是使用contains()测试Set的归属性。

  9. Map
    将对象映射到其他对象。与数组和其他Collection一样,Map很容易可以扩展到多维,而我们只需要将其值设置为Map(这些Map的值可以是其他容器,甚至是其他Map),例如:Map<Person, List< Pet >>。

  10. Queue
    队列是一个典型的先进先出(FIFO)容器。 队列常被当做一种可靠的将对象从程序的某各区域传输到另一个区域的途径。LinkedList提供了方法以支持队列的行为,并实现了Queue接口。可以将LinkedList向上转型为Queue以使用Queue的方法。offer()方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false。

    PriorityQueue
    优先级队列声明下一个弹出元素时最重要的元素(具有最高的优先级)。

  11. Foreach与迭代器
    foreach利用Itretrable接口在序列中移动,该接口包含一个能够产生Iterator的iterator()方法。所以创建任何实现Iterable的类都可以将它作用于foreach语句:

    import java.util.*;
    
    public class IterableClass implements Iterable<String> {
    
    	protected String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" ");
    
    	public Iterator<String> iterator() {
    		return new Iterator<String>() {
    			private int index = 0;
    			public boolean hasNext() { return index < words.length; }
    			public String next() { return words[ index++]; }
    			public void remove() { throw new UnsupportedOperationException(); }
    		};
    	}
    	
    	public static void main(String[] args) {
    		for(String s : new IterableClass())
    			System.out.print(s + " ");
    	}
    }
    /*Output:
    And that is how we know the Earth to be banana-shaped. 
    */
    

    在Java SE5中,大量的类都是Iterable类型,主要包括所有的Collection类(但是不包括各种Map)。

  12. Summary
    (1) 数组将数字与对象联系起来。 它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
    (2) Collection保存单一的元素,Map保存相关联的键值对。 它们可以自动调整大小。
    (3) List和数组一样建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能自动扩容。
    (4) 如果要进行大量随机访问就用ArrayList,如果要经常从表中间插入或删除元素,则应该使用LinkedList。
    (5) 各种Queue以及栈的行为由LinkedList提供支持。
    (6) Map是一种将对象(而非数字)与对象相关联的设计。 HashMap设计用来快速访问,而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
    (7) Set不接收重复元素。 HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
    (8) 新程序中不应使用过时的Vector、Hashtable和Stack。

第十二章 通过异常处理错误

  1. 基本异常
    异常情形:exceptional condition,指阻止当前方法或作用域继续执行的问题。
    异常情形和普通问题的区别就是普通问题在当前环境下能够获得足够的信息去处理错误,而异常情形在当前环境下无法获得必要的信息来解决问题,所以不能继续下去。所能做的只是从当前环境跳出,并把问题交给上一级环境,这就是抛出异常时发生的事情。

    抛出异常后:
    (1) 首先,使用new在堆上创建异常对象。
    (2) 当前的执行路径(它不能继续下去了)被终止,且从当前环境中弹出对异常对象的引用。
    (3) 同时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。

    以上的恰当的地方指的就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。

  2. 异常参数
    与java中的其他对象一样,我们用new在堆上创建异常对象,这伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造;另一个是接收字符串作为参数,以便能把相关信息放入异常对象的构造器:

    	throw new NullPointerException("t = null")
    

    可以抛出任意类型的Throwable对象,它是异常类型的根类。

  3. 捕获异常
    监控区域(guarded region):一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
    try块:

    try {
    	// guarded region
    }
    
  4. 异常处理程序
    处理抛出异常的地点称为“异常处理程序”,针对每个捕获的异常都应该有相应的处理程序,异常处理程序紧跟try块之后,以关键字catch表示:

    try {
    	// guarded region
    } catch(Type1 id1) {
    	// Handle exceptions of Type1
    } catch(Type2 id2) {
    	// Handle exceptions of Type2
    } catch(Type3 id3) {
    	// Handle exceptions of Type3
    } 
    ...
    
  5. 创建自定义异常
    自定义异常的方式通常是继承java已有的异常类,只有默认构造器的简单自定义异常,编译器将自动调用基类的默认构造器:

    class SimpleException extends Exception {
    }
    
    public class InheritingException {
        public void f() throws SimpleException{
            System.out.println("Throw SimpleException from f()");
            throw new SimpleException();
        }
    
        public static void main(String[] args) {
            InheritingException sed = new InheritingException();
            try {
                sed.f();
            } catch (SimpleException e) {
                System.out.println("Caught it!");
            }
        }
    }
    /*Output:
    Throw SimpleException from f()
    Caught it!
    */
    

    接受字符串参数构造器的自定义异常:

    class MyException extends Exception {
        public MyException() {
        }
    
        public MyException(String message) {
            super(message);
        }
    }
    
    public class FullConstructors {
        public static void f() throws MyException {
            System.out.println("Throwing MyException from f()");
            throw new MyException();
        }
    
        public static void g() throws MyException {
            System.out.println("Throwing MyException from g()");
            throw new MyException("Originated in g()");
        }
    
        public static void main(String[] args) {
            try {
                f();
            } catch (MyException e) {
                e.printStackTrace(System.out);
            }
            try {
                g();
            } catch (MyException e) {
                e.printStackTrace(System.out);
            }
        }
    }
    /*Output:
    Throwing MyException from f()
    com.MyException
    	at com.FullConstructors.f(FullConstructors.java:15)
    	at com.FullConstructors.main(FullConstructors.java:25)
    Throwing MyException from g()
    com.MyException: Originated in g()
    	at com.FullConstructors.g(FullConstructors.java:20)
    	at com.FullConstructors.main(FullConstructors.java:30)
    */
    

    在异常处理程序中,调用了Throwable类声明的printStackTrace()方法。上例中错误信息发送到了System.out中,如果选用默认的e.printStackTrace()方法,结果将送到标准错误流System.err,因为System.out可能会被重定向。

  6. 异常与记录日志
    java.util.logging工具将输出记录到日志中。不作详述。

  7. 异常说明
    异常说明使用了throws关键字,后面接一个所有潜在异常类型的列表:

    void f() throws Exp1, Exp2, Exp3... { ... }
    
  8. 捕获所有异常
    通过捕获异常类型的基类Exception来实现:

    catch(Exception e) { ... }