第11章 持有对象
如果一个程序只包含固定数量的且其生命周期都是已知的对象,那么这是一个非常简单地程序。
集合类:List、Map、Queue、Map。(容器)
一、泛型和类型安全的容器
1、ArrayList:可以自动扩充自身尺寸的数组。创建一个实例,用add()插入对象;然后用get()访问这些对象,此时需要使用索引,就像数组一样,但是不需要放括号。还要size()方法,已经有多少元素添加进来了(通过抛出运行时异常)。
2、@Suppress Warnings注解机器参数表示只有有关“不受检查的异常”的警告信息应该被抑制。
3、ArrayList<Apple>,不仅仅只是ArrayList,其中尖括号括起来的是类型参数(可以有多个),它指定了这个容器实例可以保存的类型。通过使用泛型,就可以在编译期防止错误类型的对象放置到容器中。
4、当你指定某个类型作为泛型参数时,呢并不仅限于只能将该确切类型的对象放置到容器中。向上转型也可以像作用于其他类型一样作用于泛型。
二、基本概念
1、Java容器类类库的用途是“保存对象”。
2、Collection:一个独立元素的序列,这些元素都服从一条或多条规則。List必须按照插入 的顺序保存元素,而Set不能有重复元素。Queue按照排队规則来确定对象产生的顺序(通常与它 们被插入的顺序相同)。
3、Map:一组成对的“键值对”对象,允许你使用键来査找值。ArrayList允许你使用数字来査找值,因此在某种意义上讲,它将数字与对象关联在了一起。
关联数组:映射表允许我们使用另一个对象来査找某个对象,因为它将某些对象与另外一些对象关联在了一 起(或者被称为“字典”)。
4、List<Apple> apples = new ArrayList<Apple>();
List<Apple> apples = new LinkedList<Apple>();
应该创建一个具体类的对象,将其转型为对应的接口,然后在其余的代码中都使用这个接口。这种方式并非总能奏效,因为某些类具有额外的功能,如果你需要使用这些方法,就不能将它们向上转型为更通用的接口。
5、任何继承自Collection的类的对象都可以正常工作,但是ArrayList是最基本的序列类型。
6、add()方法的名称就表明它是要将一个新的元素放置到Collection中。“要确保这个Collection 包含指定的元素”。这是因为考虑到了Set的含义,因为在Set中只有元素不存在的情况下才会添加。在ArrayList,或者任何类的List时,add()总是表示“把它放进去”,因为List不关心是否存在重复。
三、添加一组元素
1、Arrays.asList()方法接收一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。Collections.addAll()方法接收一个Collection对象,以及一个数组或是一个逗号分隔的列表,将元素添加到Collection中。
2、Collection的构造器可以接受另一个 Collection,用它来将自身初始化,因此你可以使得Aarrys.List()来为这个构造器产生输入。
但是, Collection.addAll()方法运行起来要快得多,而且构建一个不包含元素的 Collections,然后调用 Collections. addAll()这种方式很方便,因此它是首选方式。
3、Collection.addAll()成员方法只能接受另一个 Collection对象作为参数,因此它不如Arrays.asList()或 Collections. addAll()灵话,这两个方法使用的都是可变参数列表。
也可以直接使用 Arrays. asList()的输出,将其当作List,其底层表示的是数组,因此不能调整尺寸,如果你试图用add()或 delete()方法在这种列表中添加或删除元素,就有可能会引发去改变数组尺寸的尝试,因此你将在运行时获得“ Unsupported Operation(不支 持的操作)”错误。
4、Arrays.asList()方法的限制是它对所产生的List的类型做出了最理想的假设,而并没有注意你对它会赋予什么样的类型。
四、容器的打印
1、必须使用ArrayList.toString()来产生数组的可打印表示,但是打印容器无需任何帮助。ArrayList.toString()即可生成可读性很好的结果。
2、Collection在每个槽中只能保存一个元素。此类容器包括:List,它以一定特定的顺序保存一组元素;Set,元素不能重复;Queue,只允许在容器的一“端”插入元素,并从另一“端”移除对象。Map在每个槽内保存了两个对象,即键和值,对于每一个键,Map只接受存储一次,不必考虑尺寸,会自动调整尺寸。
ArrayList和LinkedList都是List类型,两者区别不仅在于执行某些类型的操作时的性能,而且LinkedList包含的操作也多于ArrayList。
HashSet、TreeSet、LinkedHashSet都是Set类型。HashSet使用相当复杂元素方式,是最快的获取元素的方式(只会关心事物是否是某个Set的成员)。TreeSet(关心存储顺序),按照比较结果的升序保存对象。LinkedHashSet,按照被添加的顺序保存对象。
HashMap、TreeMap、LinkedHashMap都是Map类型。HashMap最快的查找技术;TreeMap按照比较结果的升序保存键;LinkedHashMap则按照插入顺序保存键,同时还保留了HashMap的查询速度。
3、fill()方法可以作用域所有类型的Collection,这些类型都实现了用来添加新元素的add()方法。
五、List
1、List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List中间插入和移除元素。
2、有两种类型的List(一种可修改的序列)
(1)基本的ArrayList,长于随机访问元素,但是在List的中间插入和移除元素时比较慢。
(2)LinkedList,它通过代价较低的在List中间进行插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
3、contains()方法来确定某个对象是否在列表。
remove()方法移除一个对象。
indexOf()方法来发现对象在List中所处位置的索引编号。
List的行为根据equals()的行为而有所变化。、
4、对于LinkedList,在列表中间插入和删除都是廉价操作,对于ArrayList就是高昂的操作。(优化时,发现要担心了才解决)。
5、subList()方法允许很容易地从较大的列表中创建出一个片段,而将其结果传递给这个较大的列表的containsAll()方法时,很自然地会得到true。Collections.sort()和Collection.shuffle(),不会影响containsAll()的结果。subList()所产生的列表的幕后就是初始列表,因此,对所返回的列表的修改都会反映到初始列表中,反之亦然。
6、retainAll()方法是一种有效的“交集”操作,它保留了所有同时在copy与sub中的元素,所产生的行为依赖于equals()。
7、removeAll()移除所有元素。
set()方法在指定的索引处(第一个参数),用第二个参数替换整个位置的元素。
对于List,有个重载的addAll()可以在List的中间插入新的列表,而不仅仅只能用Collection中addAll()方法将其追加到表尾。
8、toArray()方法将创建一个具有合适尺寸的数组。
六、迭代器
1、迭代器(是一种设计模式):是一个对象,他的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。通常被称为轻量级对象:创建它的代价小。只能向前移动。
2、迭代器的限制,例如:Java的Iterator只能单向移动,这个Iterator只能用来:
(1)使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
(2)使用next()获得序列中的下一个元素。
(3)使用hashNext()检查序列中是否还有元素。
(4)使用remove()将迭代器新近返回的元素删除。
3、display()方法不包含任何有关它所遍历的序列的类型信息。
4、ListIterator:是一个更强大的Iterator的子类型,它只能用于各种List类的访问。可以双向移动。还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用ListIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
七、LinkedList
1、LinkedList执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但是随即访问操作方面要逊色一些。
2、LinkedList还添加了可以使其用作栈、队列或双端队列的方法。
3、getFirst()、element():如果list为空,则抛出NoSuchElementException。
peek():如果list为空,返回null。
4、addFirst()、add()、addLast():都是将某个元素插入到列表的尾(端)部。
removeLast()移除并返回列表的最后一个元素。
5、Queue在LinkedList的基础上添加了element()、offer()、peek()、poll()、remove()方法。
八、Stack
1、栈:FIFO先进后出的容器。叠加栈。
2、LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用。
3、peek():提供栈顶元素。
pop():移除并返回栈顶元素。
4、对stack的引用net.mindview.util.Stack所采用的方式更是可取的,而选择java.util.Stack时必须使用全限定名称。
九、Set
1、Set不保存重复的元素。HashSet,专门对快速查找进行了优化。
2、Set具有与Collection完全一样的接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。Set是基于对象的值来确定归属性。
3、HashSet使用了散列函数。TreeSet将元素存储在红黑树数据结构中。LinkedHashList因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
4、对结果进行排序,使用TreeSet来代替HashSet:
5、contains()测试Set的归属性。
7、能够产生每个元素都唯一的列表是相当有用的功能。
8、TextFile继承自List<String>,其构造器将打开文件,并根据正则表达式“\\W+”将其断开为单词,这个正则表达式表示“一个或多个字母”。
十、Map
1、将对象映射到洽谈对象的能力是一种解决编程问题的杀手锏。
2、Map与数组和其他的Collection一样,可以很容易地扩展到多维,而我们只需将其设置为Map(这些Map的值可以是其他容器,甚至是其他Map)。因此,我们能够很容易地将容器组合起来从而快速地生成强大的数据结构。
十一、Queue
1、队列是一个典型的先进先出(FIFO)的容器。队列在并发编程中特别重要,因为他们可以安全地从一个任务传输给另一个任务。
2、LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue。
3、offer()方法:在允许的情况下,将一个元素插入到队尾,或者返回false。
peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。
poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
4、自动包装机制会自动地将nextInt()方法的int结果转换为queue所需的Integer对象,将char c转换为所需的Character对象。
Queue接口窄化了对LinkedList的方法的访问权限,以使得只有恰当的方法才可以使用,因此,能访问的LinkedList的方法会变少(可以将queue转型回LinkedList,但是不建议这么做)。
5、对于Queue所继承的Collection,在不需要使用它的任何方法的情况下,就可以拥有一个可用的Queue。
6、PriorityQueue:先进先出描述了最典型的队列规则。
队列规则:在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。
先进先出声明的是下一个元素应该是等待时间最长的元素。
(1)优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。PriorityQueue是为了提供这种行为的一种自动实现。
(2)在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是可以通过Comparator来修改这个顺序。
(3)PriorityQueue可以确保当你调用peek()、poll()、remove()时,获取的元素将是队列中优先级最高的元素。
(4)让PriorityQueue与Integer、String、Character这样的内置类型一起工作易如反掌。
(5)最小的值拥有最高的优先级。
(6)Collection.reverseOrder()产生的反序的Comparator。
十二、Collection和iterator
1、Collection是描述所有序列容器的共性的根接口,是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口。
2、java.util.AbstractCollection类提供了Collection的默认实现,可以创建AbstractCollection的子类型,而其中没有不必要的代码重复。
3、使用接口描述的一个理由是它能够创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多的对象类型。在Java中,用迭代器而不是 Collection来表示容器之间的共性,但是,这两种方法绑定到了一起,因为实现 Collection就意味着需要提供iterator()方法:
4、remove是一个“可选操作”。
5、还可以选择继承的方式创建迭代器。
6、生成Iterator是队列与消费队列的方法连接在一起耦合度最小的方式。并且与实现Collection相比,在序列类上所施加的约束也少的多。
十三、Foreach与迭代器
1、foreach语法主要用于数组,也可以用在任何Collection对象。
2、Iterable接口:该接口包含一个能够能够产生Iterator的iterator(方法,并且 Iterable接口被 foreach用来在序列中移动。因此如果你创建了任何实现 Iterable的类,都可以将它用于 foreach语句中:
3、大量类都是Iterator类型,主要包括所有的Collection类(但是不包括各种Map)。
4、foreach语句可以用于数组或其他任何Iterator,但是并不意味数组肯定也是一个Iterable,而任何四栋包装也不会自动发生。
5、尝试把数组当做一个Iterable参数传递会导致失败。这说明不存在任何从数组到Iterable的自动转换,必须手动执行。
6、适配器方法的惯用法:有一个接口并需要另一个接口,时需要编写适配器。
7、Arrays.asList()产生的List对象会使用底层数组作为其物理实现。只要执行的操作会修改这个List,并且不想原来的数组被修改,那么就应该在另一个容器中创建一个副本。
十四、总结
1、Java提供了大量持有对象的方式:
(1)数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换,它可以是多维的,可以保存基本类型的数据。但是,数组一且生成,其容量就不能改变。
(2)Collection保存单一的元素,而Map保存相关联的键值对。有 Java的泛型,就可以指定容器中存放的对象类型,因此就不会将错误类型的时象放置到容器中,并且在从容器中获取元素时,不必进行类型转换。各种 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。
2、除了 TreeSet之外的所有Set都拥有与 Collection完全一样的接口。
Llst和Collection存在着明显的不同,尽管List所要求的方法都在 Collection中,另一方面,在 Queue接口中的方法都是独立的;在创建具有 Queue功能的实现时,不需要使用 Collection方法。
Map和Collection之间的唯一重叠就是Map可以使用 entrySet()和 values()方法来产生 Collection。
注意,标记接口 java.util.RandomAccess附着到了 ArrayList上,而没有附着到 LinkedList上。这为想要根据所使用的特定的List而动态修改其行为的算法提供了信息。