《Java编程思想》笔记11.持有对象
点击进入我的博客
我觉得本章名字改成容器似乎更好理解,持有对象让人感到一头雾水
我们需要在任意时刻和任意位置创建任意数量的对象,所以依靠创建命名的引用来持有对象已经满足不了需求。
Java可以用数组和其他容器类来(List、Set、Queue、Map)来解决这个问题,不同的容器类有各自的特性,满足不同的需求。
11.1 范型和类型安全的容器
Java SE5之前是没有范型的,一个容器内(以List为例)可以放置任意的对象。
public class Test { // 用@SuppressWarnings抑制编译器对“不受检查的异常”的警告 @SuppressWarnings("unchecked") public static void main(String[] args) { // (1) List strList = new ArrayList() { { add("spidersama"); add(520); } }; // (2) for (Object obj : strList) { System.out.println(((String) obj).length()); } } }
没有范型的问题:
如上所示,(1)处的代码在编译和运行的时候都没有任何问题;但是当你在(2)处需要把Object
类型强制转型成你需要的对象类型的时候,这个时候就会出现问题,因为Integer
是无法转型成String
类型的。
范型的好处
- 所以更安全的做法是,我们在创建一个容器的时候就明确它能存放的类型是什么
List<String> strList = new ArrayList<>();
。 - 编译器将阻止我们放置其他类型的对象;
- 而且你在从容器中取出对象的时候,也不必在强制转型,因为
List
知道你需要的对象类型是什么,它将会帮你自动转型。 - 我们可以将声明类型的子类放入该容器(即子类的向上转型)。
11.2 基本概念
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念。
- Collection:一个独立元素的序列。所有Collection都可以用
foreach
遍历。 - Map:一组成对的“键值对”对象,允许你使用键来查找值。
11.3 添加一组元素
Arrays.asList();
Collections.addAll();
collection.addAll()
;Collection
的构造器可以接受一个Collection
来初始化
Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3)); collection.addAll(Arrays.asList(new Integer[]{1, 2, 3})); Arrays.asList(new int[]{1, 2, 3}); Arrays.asList(1, 2, 3); Collections.addAll(collection, new Integer[]{1, 2, 3}); Collections.addAll(collection, 1, 2, 3);
注意:Arrays.asList()返回的ArrayList不可以添加元素,此ArrayList和java.util.ArrayList不是同一个类。
11.4 容器的打印
- 如果你要打印一个数组,需要用
Arrays.toString()
方法,直接打印数组显示的是[I@61bbe9ba
这种之前介绍过的格式。 - 容器由于重写了
toString()
方法,所以可以直接打印出可读性强的结果。 - 由于不同
Collection
或Map
的子类元素放置的规则和顺序不同,所以向容器内添加相同的元素,打印的结果不一定相同。 HashMap
提供了最快的查找技术,没有任何明显的顺序来保存其元素;TreeMap
按照比较结果的升序保存键;LinkedHashMap
按照插入顺序保存键,同时还保留了HashMap
的查询速度。
11.5 List
List是一种可修改的序列,它允许在创建之后添加、移除元素,或者自我调整尺寸。
有两种基本的List:
- 基本的ArrayList,它擅长随机访问元素,但是在List的中间插入和删除元素时较慢
- LinkedList,它通过代价较低的方式在List中进行插入和删除操作,提供了优化的顺序访问;但是随机访问方面相对较慢。
主要方法略
11.6 迭代器
迭代器是一个对象,他的工作是遍历并选择序列中的对象,而程序员不必知道该序列的底层结构,即将遍历序列的操作和序列底层的结构分离。
迭代器通常被成为轻量级对象:创建它的代价很小。
Java的迭代器Iterator
- Java的迭代器只能向前移动
- 使用方法
iterator()
要求容器返回一个Iterator
,Iterator
准备好返回序列的第一个元素 - 使用
next()
获取序列中的下一个元素 - 使用
hasNext()
检查序列中是否还有元素 - 使用
remove()
将迭代器新近返回的元素删除
更强大的迭代器 ListIterator
- 它是
Iterator
的子类型,只能用于List
的访问。 - 它可以双向移动
- 它可以返回当前位置前一个元素和后一个元素的索引(即下标)
- 可使用时
set()
方法替换访问过的最近的元素 - 可以使用
listIterator(int index)
直接创建一个指向索引处的ListIterator
11.7 LinkedList
如前所述,LinkedList
也像ArrayList
一样实现了基本的List
接口,但是它执行插入和移除时比ArrayList
要更高效,但是在随机访问操作方面要慢。LinkedList
还添加了可以使其用作栈、队列或双端队列的方法(是Queue
的子类)。
LinkedList
中的相同方法
- 正是由于LinkedList添加了栈和队列的方法,使得内部多了一些功能相同但名称不同(为了覆盖父类)的方法
- getFirst() == element() ≈ peek():都是返回第一个元素,但是在空
List
的时候处理不一样 - removeFirst() == remove() ≈ poll():都是移除并返回列表的头,但是在空
List
的时候处理不一样 - addFirst() == addLast():都将某个元素插入队尾
11.8 Stack 栈
栈是一种先进后出(Last In First Out,LIFO)的容器(数据结构)。
LinkedList能实现栈所有的功能。
主要方法
- peek():返回栈顶元素
- pop():返回并移除栈顶元素
- push():元素入栈
- empty():栈是否为空
11.9 Set 集合
Set
不保存重复的元素。Set
与Collection
有完全一样的接口(方法),因此没有任何额外的功能,实际上Set
就是Collection
只是行为不同(这就是继承和多态思想的典型应用:体现不同的行为)。
常用的几种Set
- HashSet:使用的是散列函数
- TreeSet:将元素存储在红-黑树数据结构中。TreeSet默认是按照字典序排序的;初始化TreeSet的时候可以设定排序的方式,如
String.CASE_INSENSITIVE_ORDER
就是按照字母序排列;你也可以写一个你自己的比较器Comparator
。 - LinkedHashSet:是HashSet的扩展,但是元素顺序是按照放插入顺序保存的。
11.10 Map
键值对
11.11 Queue 队列
队列是一个典型的先进先出(First In First Out,FIFO)的容器。
队列通常被当作一种可靠的将对象从程序的一个区域传输到另一个区域的途径,尤其在并发编程中十分重要。
主要方法
offer()
:将元素插入队尾。add()
:同offer()
,但是当超出队列长度当时候抛出异常。peek()
:不移除的返回队头元素;为空时返回null。element()
:同peek()
,为空时抛出NoSuchElementException
。poll()
:移除并返回队头元素,为空时返回null。remove()
:同poll()
,为空时抛出NoSuchElementException
异常。
11.11.1 PriorityQueue
优先级队列:按照优先级的顺序维护的队列。
当你在PriorityQueue
上调用offer()
方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中自然顺序,但是你可以通过提供自己的Comparator
来修改这个顺序。PriorityQueue
可以确保当你调用相关方法时,获取的元素将是队列中优先级最高的元素。
11.12 Collection和Iterator
Java中,实现Collection
就必须提供iterator()
方法,这有的时候会带来麻烦。
直接生成Iterator
是将队列与消费队列的方法链接在一起耦合度最小的方式,并且与实现Collection
相比,它在序列类上所施加的约束也少得多。
11.13 Foreach与迭代器
foreach
可以用于数组和所有Collection
对象,之所以能够工作,是因为Collection
继承了Iterable
接口。- 数组不是
Iterable
。 Iterable
接口包含一个能够产生Iterator
的iterator()
方法,并且Iterable
接口被foreach
用来在序列中移动。换言之,任何实现了Iterable
接口的类,都可以用与foreach
语法。
11.13.1 适配器方法惯用法
如果需要多个foreach
遍历一个类的方法,例如该类需要支持向前和向后遍历。这是只实现Iterable
是不行的,可以编写其他返回类型为Iterable
的方法来满足foreach
语句。这就是编写适配器。
// 反向遍历部分代码 public class Test<E> extends ArrayList<E> { public Test(Collection<? extends E> c) { super(c); } public Iterable<E> reverse() { return new Iterable<E>() { @Override public Iterator<E> iterator() { return new Iterator<E>() { int index = size() - 1; @Override public boolean hasNext() { return index >= 0; } @Override public E next() { return get(index--); } }; } }; } }
10.14 总结
- 数组将数字与对象联系起来。
- Collection保存单一的元素,而Map保存相关联的键值对。
- List也建立数字索引与对象的关联,List能够自动扩充容量。
- 如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中间出入或删除元素,则应该使用LinkedList。
- 各种Queue以及栈的行为,由LinkedList提供支持。
- Map是一种将对象(而非数字)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
- Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素。
- 不要使用已过时的Vector、Hashtable和Stack。
容器分类