第十一章 持有对象
最后一阶段了,又是有难度的一章,加油。
如果一个程序只包含固定数量的且生命期都是已知的对象,那么一定是一个非常简单的程序。
1 泛型和类型安全的容器
例子中使用了@SuppressWarnings注解。它的作用是抑制编译器产生的告警信息。
抑制一个类型的警告:@SuppressWarnings("unchecked")
抑制多个类型的警告:@SuppressWarnings(value = {"unchecked", "rawtypes"})
抑制多有类型的警告:@SuppressWarnings("all")
该注解目标为类、字段、函数、函数形参、构造函数和函数的局部变量。建议注解声明在最接近告警发生的位置。
通过例子告诉我们范型有效的防止错误的类型对象放入到容器中。
2 基本概念
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
a.Collection:一个独立元素的序列,这些元素都服从一些规则。例如,List必须按顺序插入,Set不能有重复的元素,Queue是队列。
b.Map:一组键值对对象,允许使用键来查找值。
3 添加一些元素
主要讲了四种添加元素的方法:
a.Collection的构造函数可以接受另一个Collection对象,用它来初始化自身。
b.Arrays.asList()方法的参数可以是数组或变长参数,返回的是List对象,但是在这种情况下,其底层表示的是数组,因此不能调整尺寸,当调用它的add()方法和remove()(实测书中delete()方法会报错,应为remove())。
c.Collections.addAll()方法的参数是一个Collection对象、数组或变长参数,将元素添加到Collection对象中。
d.Collection.addAll()只能接口另一个Collection对象作为参数。
书中的例子告诉我们当将Light和Heavy类型的对象作为参数传入Arrays.asList()方法中时,他会创建List<Powder>而不是List<Snow>,这个和预期不同,因此解决办法是再Arrays.asList()中插入一条线索如:Arrays.<Snow>asList()。
例子如图:
4 容器的打印
我们必须通过Arrrays.toString()方法来打印数组的内容,但是打印容器无需任何帮助。
Collection容器主要包括:List、Set、Queue。
List:它以特定的顺序保存一组元素。ArrayList和LinkedList都是List的类型。他们都是按照被插入顺序保存元素,ArrayList擅长于随机访问元素,但是在中间插入和移除元素较慢。LinkedList优化了顺序访问,并且在List中间进行插入和删除操作代价较低,但是随机访问较慢,而且LinkedList包含的操作也多与ArrayList。
Set:它的元素不能重复。HashSet、TreeSet和LinkedHashSet都是Set类型。他们的储存方式不同,HashSet也是相当复杂。不过这类储存方式相当快。TreeSet按照比较结果的升序保存对象。LinkedHashSet按照添加的顺序保存对象。
Queue:它只允许在容器的一段插入对象,并从另一端移除对象。
Map就像一个简单的数据库,我们可以用键来查找对象。HashMap、TreeMap和LinkedHashMap是Map的三种风格。TreeMap按照比较结果的升序保存键。LinkedHashMap按照添加的顺序,同时保留了HashMap的查询速度。
5 List
可以使用contains()方法来确定某个对象是否在列表中:
如果想移除一个对象,则可以将这个对象的引用传递给remove()方法,无此对象会导致删除失败,方法返回false。
如果有一个对象的引用,可以使用indexOf()方法来发现对象在List中的索引号,无此对象返回-1。
可以在List中间插入,ArrayList中间插入是很昂贵的,而LinkedList中间插入是很廉价的。
注意:用元素索引值来删除元素,与移除对象引用相比,它更加直观,因为在使用索引值时,不必担心equals()的行为。(确定一个元素是否属于List、发现某个元素的索引以及从List中移除一个元素,会用到equals()方法。所以List的这些行为会根据持有对象的equals()行为不同有所变化)
containsAll()判断不在乎顺序。
set()方法替换掉指定索引位置的元素。
retainAll()仅在List中保留指定collection中所包含的元素。
toArray()方法将Collection转换为数组。无参数版本返回的是Object数组,有参数的版本可以向其传递目标类型的数组,那么它将产生指定类型的数组(而不是Object数组)。如果参数数组太小,存放不下目标类型的数据,那么它将创建合适尺寸的数组。
Collections.sort()是排序,Collection.shuffle()是打乱。
6 迭代器
迭代器通常被称为轻量级对象,创建它的代价非常小。
Java中的Iterator只能单向移动,它只能用来:
a.使用容器的iterator()方法要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
b.使用next()获得序列中的下一个元素。
c.使用hasNext()检测序列中是否还有元素。
d.使用remove()将迭代器新近返回的元素删除。
迭代器(也是一种设计模式)的概念可以用于使得客户端程序员不必知道或关心容器类的底层结构并且将遍历序列的操作与序列底层的结构分离。
ListIterator
只能用于各种List类的访问。ListIterator可以双向移动,而Iteraotr只能向前移动。
nextIndex()如果列表迭代器在列表的结尾,则返回列表的大小。
previousIndex()如果列表迭代器在列表的开始,则返回 -1。
可以使用set()方法替换访问过的最后一个元素。
调用listIterator()方法产生一个指向List开始处的ListIterator,也可以调用listIterator(n)方法创建一个开始就指向 索引为n的元素处的ListIterator。
7 LinkedList
LinkedList在实现了基本的List接口的功能之上还添加了可以使其用作栈、队列或双端队列的方法。
有些方法并没有差别、有些只有细微的差别,它们有不同的名字主要是因为这些名字在特定的环境中更加合适。
getFirst()、element()和peek()都返回列表的第一个元素且不删除它们,如果列表为空前两个方法会抛出NoSuchElmentException,peek()方法则会返回null。
removeFirst()、remove()和poll()方法都是移除并返回列表的头,如果列表为空前两个会抛出NoSuchElementException,poll()方法会返回null。
ddFirst()与add()和addLast()相同,都将某个元素插入到列表的尾部。
removeLast()移除并返回列表的最后一个元素。
8 LinkedList与ArraysList
都可自动扩容。
LinkedList
具有能够直接实现栈(Stack)的所有功能的方法,因此可以直接将LinkedList作为栈使用。
LinkdedList
也提供了支持队列(Queue)行为的方法,并且实现了Queue接口,所以也可以用作Queue。
ArrayList
底层是数组结构,即连续存储空间,所以读取元素快。因可自动扩容,所以可以把ArrayList
当作“可自动扩充自身尺寸的数组”看待。
LinkedList
是链表结构,所以插入元素快。
9 Stack(栈)
一个栈需要的方法主要有push(T t)入栈、poll()出栈和peek()返回栈顶元素但不删除,另外还需要有一个empty()方法判断栈是否为空。
10 Set
TreeSet的构造函数允许传入一个比较器(Comparator) 作为参数用于给元素排序。
究竟比较器是啥,还得学完第13章才能知道。
11 Map
对象可以返回它的键的Set——keySet(),返回值的Collection——values(),返回键值对的Set——entrySet()。
具体的暂时不细说,不过记住Map 将对象映射到其他对象的能力是一种解决编程问题的杀手锏。
12 Queue
如果想使用队列,可以创建LinkedList对象并向上转型为Queue,这样Queue接口窄化了LinkedList的访问权限。
offer()方法在允许的情况下将元素插入到队尾,peek()和element()在不删除元素的情况下返回队头,如果队列为空前者返回Null,而后者抛出NoSuchElementException异常。
poll()和remove()方法返回队头并删除元素,队列为空时前者返回Null,后者抛出异常。
PriorityQueue类队列中,不是根据插入的时间来弹出元素,而是根据元素的优先级,优先级高的先弹出。当用offer()向PriorityQueue插入对象后,它会在队列中根据自然顺序进行排序,可以向PriorityQueue传入自定义的Comparator来改变默认的顺序。
13 Collection和Iterator
在Java中,Collection
是描述所有序列容器的共性的根接口,它可能会被 认为是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口。
Java将两种方法绑定到了一起,因为实现Collection
就意味着需要提供iterator()
方法。
14 Foreach与迭代器
foreach
语法用于任何实现了Iterable
接口的类。Collection
接口扩展了Iterable
接口,所以所有Collection
对象都适用foreach
语法。
数组也可以应用于foreach语句,但是注意数组并没有实现Iterable接口。
Arrays.asList()方法中,如果修改方法返回的List,那么作为参数传入的数组也将会被修改,此种情况容易被忽略。
15 容器的元素类型
泛型之前的容器不能持有基本类型元素,显然数组是可以的。但是有了泛型,容器就可以指定并检查它们所持有对象的类型,并且有了自动包装机制,容器看起来还能够持有基本类型。
在Java中,任何基本类型都不能作为类型参数。因此不能创建ArrayList<int>
或 HashMap<int, int>
之类的东西。但是可以利用自动包装机制和基本类型的包装器来解决,自动包装机制将自动地实现int
到 Integer
的双向转换:
// ListOfInt.java
import java.util.*;
public class ListOfInt{
public static void main(String[] args){
// 编译错误:意外的类型
// List<int> li = new ArrayList<int>();
// Map<int, Interger> m = new HashMap<int, Integer>();
List<Integer> li = new ArrayList<Integer>();
for(int i = 0; i < 5; i++){
li.add(i); // int --> Integer
}
for(int i : li){ // Integer --> int
System.out.print(i + " ");
}
}
}/* Output:
0 1 2 3 4
*/
如我练习题中所说,自动封装的类型要先拆包再封装。
16 总结
新程序中不应该再使用已经过时的Vector、Hashtable和Stack。
书中关系图如下:
总结起来共有四种容器:List、Set、Queue和Map。每一种各有两三个实现。常用的在图中用粗线框表示,虚线框表示接口,实线框表示类。空心虚线箭头接口或者类实现了上层接口,实心虚线箭头表示某个类可以生成箭头所指向的类。
这里还有一张图类似不过更方便查看(图片来自于网络)图片如下:
学习笔记暂时告一段落,接下来继续开始学习从第十二章开始。