Thinking in Java 4th chap11笔记-持有对象
持有对象
1.通过使用 泛型 ,就可以在编译期防止将错误类型的对象放置到容器中。
2.Object默认的toString()方法将打印类名,@后面跟随该对象的散列码的无符号16进制表示 .这个散列码是通过hashCode()方法产生的。
注 :默认的hashCode()方法返回对象的内存地址。
3.Java 容器类类库 的用途是保存对象,并将其划分为两个不同的概念。
1. Collection .一个独立的元素序列。这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素。Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)
2. Map ,一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在了一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为关联数组。因为它将某些对象与另外一些对象关联到了一起。或者被称为字典,因为你可以使用键对象来查找值对象,就像是在字典中使用单词来定义一样。Map是强大的编程工具。
3.使用接口的目的在于如果你决定去修改你的实现,你所需要的只是在创建中修改它。如:
List<Apple> apples = new ArrayList<Apple>();
更改为 List<Apple> apples = new LinkedList<Apple>();
因为在理想情况下,你编写的大部分代码都是在于这些接口打交道,并且你唯一需要指定所使用精确类型的地方就是在创建的时候。注意:这种方式并非总能奏效。因为某些类具有额外的功能。例如LinkedList具有在List接口中未包含的额外方法,而TreeMap中也具有在Map接口中未包含的方法。如果你需要使用这些方法,就不能将它们向上转型为更通用的接口。
4. Collection接口 概括了序列的概念,一种存放一组对象的方式。所有的Collection都可以使用foreach语法遍历。ArrayList是最基本的序列类型。在Set中只有元素不存在的情况下才会添加。而List不关心是否重复。
5.java.util包中的 Arrays 和 Collections 类中都有很多实用方法。如可以在一个collection中添加一组元素。
1.Arrays.asList(
2.Collections.addAll(
注:参数可为逗号分割的元素列表.(使用可变参数).另外所有的Collection类型都包括传统的addAll方法.
3.Collection的构造器可以接受另一个Collection,用它来将自身进行初始化。但是Collection.addAll方法要运行起来要快的多。而且构建一个不包含元素的Collection,然后调用Collection.addAll方法这种方式很方便,因此它是首选方式。
4.Collection.addAll方法只能接受另外一个Collection对象作为参数,因此它不如Arrays.asList和Collections.addAll灵活,因为这个两个方法使用的都是可变参数列表。
5.你可以直接使用Arrays.asList的输出,将其当做List,但是在这种情况下,其底层表示的是数组,因此不能调整尺寸。如果你试图调用add或者delete方法在这种列表添加或者删除元素,就可有可能会引发去改变数组尺寸的尝试,因此你将在运行时获得UnsupportedOperationException错误。
6.Arrays.asList方法的限制是它对所产生的List类型的类型做了最理想的假设,而并没有注意你对它会赋予什么样的类型。有时这就会引发问题.
public static void main(String...args)
{
//因为asList的三个参数都是直接继承自己Snow,所以没有问题
List<Snow> snow1 = Arrays.asList(new Powder(),new Crusty(),new Slush());
// 注意 ,这时候就编译报错了。虽然Light和Heavy直接继承Powder,间接继承Snow
//此时编译错误为Type mismatch: cannot convert from List<Powder> to List<Snow>;因为参数只有Powder类型,所以其推断会创建List<Powder>而不是List<Snow>
//List<Snow> snow2 = Arrays.asList(new Light(),new Heavy());
//改成List<Powder>就没有问题,这样过于理想的假设,它直接推断类型为Power,所以产生的List类型也为List<Powder>
List<Powder> powder = Arrays.asList(new Light(),new Heavy());
//使用Collections.addAll方法就不会出现混淆
List<Snow> snow3 = new ArrayList<Snow>();
//这样不会报错,因为它从第一个参数snow中已经列表到了目标类型是什么
Collections.addAll(snow3, new Light(),new Heavy());
//注意这种nx的用法,插入一条线索,以告诉编译器对于由Arrays.asList产生的List类型,实际的类型应该是什么。这称为显示类型参数说明。
List<Snow> snow4 = Arrays.<Snow>asList(new Light(),new Heavy());
}
4.容器的 打印
1.你必须使用 Arrays.toString() 来产生数组的可打印表示。但是打印容器无需任何帮助。
2.ArrayList和LinkedList都是List类型。他们都按照插入的顺序保存元素。两者的不同之处不仅在于执行某些类型操作的性能,而且LinkedList的操作也多于ArrayList.
3.HashSet,TreeSet和LinkedHashSet都是Set类型。每个相同的项只保存一次。不同的Set实现存储元素的方式也不同。HashSet是最快的获取元素方式,因此存储的顺序看起来毫无意义。通常你只会关心某事物是否是某个Set成员。而不会关心它在set中出现的顺序。如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存对象。或者使用LinkedHashSet,它将按照被添加的顺序保存对象。
4.HashMap,TreeMap,LinkedHashMap,HashMap提供了最快的查找技术,也没有按照任何明显的顺序来保存其元素。TreeMap按照比较结果的升序保存键。而LinkedHashMap则按照插入顺序保存键,同时也保留了HashMap的查询速度。不必指定Map或者考虑其尺寸,它会自动的调整尺寸。
5. List
1.List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量方法。使得可以在List的中间插入和移除元素。
有两种类型的List:1.基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时较慢。2.LinkedList,它通过代价较低的在List中间进入的插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
2.当确定一个元素是否属于某个List,发现某个元素的索引,以及从某个List中移除一个元素时,都会用到equals方法。因此为了防止意外,必须意识到List的行为根据equals()的行为而有所变化。
3.对于LinkedList,在列表中间插入和删除都是廉价操作。但是对于ArrayList,这可是代价高昂的操作。这是否意味着你应该永远不要在ArryList的中间插入元素并最好切换到LinkedList.不,这仅仅意味着,你应该意识到这个问题。如果你开始在某个ArrayList的中间执行很多插入操作,并且你的程序开始变慢,那么你应该看看你的List实现有可能就是罪魁祸首。优化是一个很棘手的问题,最好的策略就是置之不顾。直到你发现需要担心它了。
4.subList方法产生的sub和原list都指向同一个引用。对sub的修改会影响到初始列表中,反之亦然.
5.retainAll方法是一种有效的"交集操作".注意,其所产生的行为也依赖于equals方法。removeAll方法也是基于equals的。
6. //List的toArray方法,注意不带参数的toArray方法返回的是Object[]
Object[] array = list4.toArray();
// 注意 ,如果调用带参数的toArray方法,它将产生参数类型的数组.注:如果参数数组存储不下list4的大小,toArray方法将创建一个具有合适尺寸的数组。
Integer[] array2 = list4.toArray(new Integer[0]);
//注:个人喜欢这种用法,这样就必须在去创建一个合适尺寸的数组了
Integer[] array3 = list4.toArray(new Integer[list4.size()]);
6. 迭代器:
1.迭代器是一个对象,它的工作是遍历并选择序列中的对象。而客户端程序员不必知道或者关心该序列底层的结构。此外,迭代器通常被称作轻量级对象,创建它的代价小。因此,经常可以看到对迭代器有些奇怪的限制,例如Java的Iterator只能单向移动。这个Iterator只能用来:
1.使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
2.使用next()获得序列的下一个元素。
3.使用hasNext()检查序列中是否还有元素
4.使用remove()将迭代器新近返回的元素删除。
1.有了Iterator,就必须为元素数量操心了。那是由next()和hasNext()关心的事情。
2.如果你只是向前遍历List,并不打算修改List对象本身,那么foreach语法会显得更加简洁。
3.Iterator还可以移除由next()产生的最后一个元素。所以必须在调用remove之前必须先调用next().注:remove是可选的操作方法,即不是所有的Iterator实现都必须实现该方法。但是标准Java类库实现了remove().
4.接收对象容器并传递它,从而在每个对象上都执行操作。这种思想很强大。
5.Iterator真正的威力:能够将遍历序列的操作与序列底层的结构分离。正由于此,我们有时会说。迭代器统一了对容器的访问方式。
6.ListIterator:是一个更加强大Iterator的子类型。它只能用于各种List类的访问。Iterator只能向前移动,而ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指定的当前位置的前一个和后一个元素的索引。并且可以使用set方法替换它访问过的最后一个元素。你可以调用listIterator()方法产生一个指向List开始处的ListIterator.并且还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator.
7. LinkedList 也像ArrayList一样实现了基本的List接口。但是他执行某些操作(在List的中间插入和移除)时比ArrayList更高效。但是在随机访问操作方面却要逊色一些。
LinkedList还添加了可以使其作用于栈,队列或者双端队列的方法。
这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用,特别是Queue中。
1.getFirst()和element()完全一样,他们都返回列表的头,即第一个元素。而并移除它。如果List为空,则抛出NoSuchElementException.
2.peek方法同1上面两个方法稍许有些差异,它在列表为空时返回null。
3.removeFirst与remove方法也是完全一样的,他们移除并返回列表的头,而在列表为空时抛出NoSuchElementException.
4.poll同3上面的两个方法稍许有些差异,它在列表为空时返回null.
5.addFirst和add与addLast相同,都是将某个元素插入到列表的尾(端)部。
注:add同addLast,将指定元素添加到此列表的结尾。不过addFirst则为将指定元素插入此列表的开头
6.removeLast移除并返回列表的最后一个元素。
7.如果你浏览一下Queue接口,你就会发现它在LinkedList的基础上添加了element(),offer(),peek(),pool()和remove()方法。以其可以称为一个Queue的实现。
8. Stack -栈:
1.栈通常是指后进先出的容器。(LIFO).有时栈也被称为叠加栈。因为最后压入栈的元素,第一个弹出栈。经常用来来类比栈的事物的是装有弹簧的储放器中的自助餐托盘。最后装入的托盘总是最先被拿出来使用的。
2.LinkedList具有能够直接实现栈的所有功能的方法。因此可以直接将LinkedList作为栈使用。不过,有时候一个真正的栈更能把事情讲清楚。如果你只需要栈的行为,这里使用继承就不合适了。因为这样会产生具有LinkedList的其他方法的所有的类。Java1.0的设计者在创建java.util.Stack时,就犯了这个错误。
3.使用自己写的Stack的时候,创建实例的时候,注意完整指定包名或者更改这里的名称,否则就有可能与java.util.Stack发生冲突。所以说如果在demo中都使用了二者,则必须用包名防止冲突。
4.自己设计的Stack和java.util.Stack二者有相同的接口,但是在java.util中没有任何公共的Stack接口。这可能是因为在Java1.0中的设计欠佳的最初的java.util.Stack类占用了这个名字。尽管已经有了java.util.Stack,但是LinkedList可以产生更好的Stack.因此推荐自己设计的Stack.
9. Set-
1.不保存重复的元素。如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象。Set中最常被使用的是测试归属性。你可以很容易的询问某个对象是否在Set中。正因为如此,查找就成了Set中最重要的操作。你通常会选择一个HashSet的实现。它专门对快速查找做了优化。
2.Set具有和Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。这是继承与多态思想的典型应用,表现不同的行为。Set是基于对象的值来确定归属属性的。
3.HashSet使用了散列。HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。TreeSet将元素存储在红黑树数据结构中。而HashSet使用的是散列函数。LinkedHashSet因为查询速度的原因也使用了散列。但是看起来它使用了链表来维护元素的插入顺序。
4.如果你想对结果排序,一种方式是使用TreeSet代替HashSet.
5.TreeSet的构造器可传入Comparator,如String.CASE_INSENSITIVE_ORDER(与 compareToIgnoreCase 相同)
10. Map:
1.containsKey/containsValue
2.可以很容易的扩展到多维,而我们只需要将其值设置为Map(这些Map的值可以是其他容器,甚至是其他Map)
3.Map可以返回它的键的Set,它的值的Collection,或者它的键值对的Set
11. Queue:
1.队列是一个典型的先入先出FIFO的容器,即从容器的一端放入事物,从另一端取出。并且事物放入容器的顺序与取出的顺序是相同的。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,因为他们可以安全的将对象从一个任务传输给另一个任务。
2.LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口。因此LinkedList可以当做Queue的一种实现。可通过将LinkedList向上转型为Queue.
3.offer方法是与Queue相关的方法之一。它在允许的情况下将一个元素插入到队尾或者返回false.peek与element都将在不移除的情况下返回队头,但是peek方法在队列为空时返回null,而element则抛出NoSuchElementException;poll和remove方法将移除并返回队头。但是poll在队列为空时返回null,而remove方法则会抛出NoSuchElementException.
抛出异常 返回特殊值
插入 add offer
移除 remove poll
检查 element peek
4.Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用。因此,你能够访问LinkedList的方法会变少。注意:与Queue相关的方法提供了完整而独立的功能;即对于Queue继承的Collection,在不需要使用它的任何的情况下,就可以用有一个可用的Queue.
12. PriorityQueue:
1.先入先出描述了最典型的队列规则。队列规则是指在给定一组队列中的元素的情况下,确定下一个弹出队列元素的规则。先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素,具有最高的优先级。如果构建了一个消息系统,某些消息比其他消息更重要,因此应该更快得到处理,那么他们何时得到处理就与他们何时达到无关。PriorityQueue添加到了JavaSE5中,是为了提供这种行为的一种自动实现。
2.当你在PriorityQueue中调用offer对象插入一个对象时,这个对象会在队列中被排序(注:这实际上依赖于具体实现.优先级队列算法通常会在插入时排序,维护一个堆。但他们也可能在移除时选择最重要的元素。如果对象的优先级在它在队列时中等待时可以进行修改,那么算法的选择就很重要了)。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可以确保你在调用peek,poll,remove方法时获得的元素将是队列中优先级最高的元素。
3.PriorityQueue与Integer,String,Character这样的内置类型工作易如反掌。因为这些类已经内建了自然排序。
4.Collections.reverseOrder()产生反序,新增至JavaSE5,返回一个Comparator的比较器
13. Collection和Iterator
1.Collection是描述所有序列容器的共性的根接口。它有可能被认为是一个附属接口,即因为要表示其他若干接口的共性而出现的接口。另外java.util.AbstractCollection类提供了Collection的默认实现,(此类提供 Collection 接口的骨干实现,以最大限度地减少了实现此接口所需的工作),而其中没有不必要的代码重复。
2.使用接口描述的一个理由是它可以使我们能够创建更加通用的代码。通过针对接口而非具体实现编码,我们的代码可以作用于更多的对象类型。因此,如果我的方法接口一个Collection,那么该方法就可以应用于任何实现了Collection的类。这也使得一个新类可以选择去实现Collection接口,以便我的方法可以使用它。
3.有一点很有趣,C++标准库中并没有其容器的任何公共基类,容器之间的所有共性都是通过迭代器达成的。在Java中,遵循C++的方式看似明智,即用迭代器而不是Collection来表示容器之间的共性。但是这两种方法绑定到了一起,因为实现Collection就意味着需要提供iterator方法.
4.使用Collection接口和Iterator都可以与底层的特定容器实现解耦。事实用Collection要更方便一些,因为它是Iterable类型,从而可以使用foreach接口,从而使代码更加清晰。
5.不过你要实现一个不是Collection的外部类时,由于让它去实现Collection接口可能非常困难或者麻烦,因此使用Iterator就变得非常吸引人了。例如我们通过一个现有类来创建一个Collection实现,那么我们必须实现所有的Collection方法,即使我们根本无法用到,也必须如此。尽管这可以通过继承AbstractCollection很容易的实现,但是你无论如何还会被强制去实现iterator和size,以提供AbstractCollection中没有实现但是AbstractCollection的其他方法会使用到的方法。
6.如果你要实现Collection就必须要实现iterator并且只拿实现iterator与继承AbstractCollection相比,花费的代价只是略微减少。但是如果你的类已经继承了其他类,那么你就不能再继承AbstractCollection了。在这种情况下,你就必须要实现Collection,就必须实现该实现接口的所有方法。此时,继承并提供创建迭代器的能力就会显得容易的多。
7.生成iterator是将队列与消费队列的方法连接在一起耦合度最小的一种方式。并且与实现Collection相比,它在序列类上所施加的约束也少很多。
14. Foreach与迭代器
1.到目前为止,foreach语法主要用于数组.但是它也可以用于任何Collection对象。能够与foreach一起工作,是所有Collection对象的特性。之所有能够工作,是因为JavaSE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator方法,并且Iterable接口被foreach用来在序列中移动。因此,如果你创建了任何实现Iterable的类,都可以将它用于for-each语句中。
2.在JavaSE5中,大量的类都是Iterable类型,主要包括所有的Collection类,但是不包括各种map。
3.Map#entrySet()产生一个由Map.Entry的元素构成的Set,而这个Set是一个Iterable。因此它可以用于for-each。
4.for-each语句可以用于数组或其他任何Iterable,但是这不意味着数组肯定也是一个Iterable。
5.适配器方法惯用法:
1.如果你有一个Iterable类, 你想要添加一种或者多种在for-each语句中使用这个类的方法,应该怎么做呢?加入你希望可以选择向前的或者向后的方向迭代一个列表。如果直接继承这个类并覆盖iterator方法,只能替换现有的方法而不能实现选择。
2.一种解决方案是使用适配器的惯用法。适配器部分来自设计模式,因为你必须提供特定接口以满足for-each语句。当你有一个接口并需要另一个接口的时候,编写适配器就可以解决问题。这里,我们希望在默认迭代的基础上添加产生反向迭代的能力。因此我们不能使用覆盖,而是需要添加一个能够产生Iterable对象的方法。该对象可以用于for-each语句,这使得我们可以提供使用多种foreach的方式。
3.Arrays.asList方法产生的List对象会使用底层数组作为其物理实现是很重要的。只要你执行的操作会修改这个list,并且你不想原来的数组被修改,那么你应该在另一个容器中创建一个副本。
15.总结:
Java提供了大量持有对象的方式
1.数组将数字与对象关联起来,它保存类型明确的对象,查询对象时不需要对结果做类型转换。可以是多维的,可以保存基本类型的数据。但是数组一旦生成,其容量就不能改变。
2.Collection保存单一的元素,而Map保存相关联的键值对。有了Java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中并且从容器中获取元素的时候不必执行类型转换。各种Collection和各种Map都允许你在向其中添加更多元素时自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细的执行基本类型到容器中所持有的包装器类型之间的双向转换。
3.向数组一样,List也建立数字索引与对象的关联,因此数组和list都是排好序的容器。List能够自动扩充容量。
4.各种Queue的行为以及栈的行为,由LinkedList提供支持。
5.Map是一种将对象而非数字与对象关联的设计。HashMap用来设计快速访问,而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
6.如果要进行大量的随机访问,就是用ArrayList;如果要经常从表中间插入和删除元素,就应该使用LinkedList.
7.Set不接受重复元素,HashSet提供最快的查询速度,而TreeSet保持元素出于排序状态。LinkedHashSet以插入顺序保持元素。
8.新程序中不应该使用过时的Vector,Stack和Hashtable.
16.Java容器的 类图:
Iterator Collection Map
ListIterator List Set Queue HashMap TreeMap
ArrayList LinkedList HashSet TreeSet PriorityQueue LinkedHashMap
LinkedHashset
Comparable<->Comparator Collections/Arrays
1.除了TreeSet之外的所有Set都拥有与Collection完全一样的接口。List和Collection完全不同,尽管List要求的方法都在Collection中。另一方面,在Queue接口中的方法都是独立的,在创建具有Queue功能的实现时,不需要使用Collection方法。最后Map和Collection之间的唯一重叠是Map可以使用entrySet,keySet和values方法来产生Collection.
2.标记接口RandomAccess附着到了ArrayList上面。而没有附着到LinkedList上面,这为想要根据所使用的特定的List而动态修改其行为的算法提供了信息。
3.容器类库一直以来都是设计难题,解决这些难题涉及到要去满足经常彼此之间互为牵制的各方面需求。因此,你应该学会中庸之道。
部分源码:

































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































