Ⅺ 持有对象
大部分情况下,对象的存在数量和生命周期都是未知的,程序总是根据运行时才能得到的某些条件去创建新对象。为了解决这个问题,我们需要拥有在任意时刻和任意位置,创建任意数量的对象的能力。
Java有多种方式来保存对象的引用。比如说数组,但是数组具有固定的尺寸,这一特性使他十分局限。
Java类库中提供了一套相当完整的容器来解决这个问题,Collection
只是这个类库中的一个特殊子集。在这个子集中,基本类型有List
,Set
,Map
,Queue
。
11.1 泛型和类型安全的容器
在没有使用泛型时,存在一个ArrayList中的都是Object,当你取出某个自认为是某种(假设为Apple类型)类型的对象时,其实只是Object的引用,必须将其转型为Apple。而假如你在这个ArrayList中放入了其他类型(比如Orange类型),那么取出时假如也将其转型为Apple,那么将会出现错误。
使用泛型来创建类其内部十分复杂。但是,应用预定义的泛型通常很简单。比如,可以这样使用ArrayList<Apple>
,它指定了这个容器所保存的类型。(类型参数可以有多个)通过使用泛型,可以在编译期防止将非规定的类型对象放置到容器内。同时,从容器中取对象时(其实是对象的引用),类型转换也不是必须的了。向上转型一样也可以作用于泛型。
11.2 基本概念
我们对这段语句一定不陌生。
List<Apple> apples=new ArrayList<Apple>();
此时ArrayList已经被向上转型为List。使用父类接口的目的是,如过你想要去修改你的实现方式,你只要修改在创建它时的句子。
List<Apple> apples=new LinkedList<Apple>();
但是这种方式并不总能奏效,比如LinkedList具有List中未包含的额外方法。如果需要使用到这些额外方法,那么就不能对其进行向上转型。
11. 5 List
两种类型的List:
- ArrayList:擅长于随机访问元素,但是在中间插入和删除时比较慢。
- LinkedList:通过较低的成本在List中进行插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问时相对较慢,但是它的特性集比ArrayList更大。
11.6 迭代器
从更高的角度来看,发现容器存在一个缺点:要使用容器,必须对容器的确切类型编程。
如果对不同容器的相似操作的代码需要复用,代码只是在操作容器,并不关心容器的类型,那么如何才能“忽视”容器之间的差异?
迭代器(也是一种设计模式)的概念可以解决这个问题。迭代器是一个对象,它的工作是遍历并选择序列中的对象,它十分轻量级,创建它的开销也非常小。因此,迭代器也有很多的限制:
- 单向移动
- 使用
iterator()
要求容器返回一个Iterator类型。Iterator将准备好返回序列的第一个元素。 - 使用
next()
获取序列中的下一个元素。 - 使用
hasNext()
检查序列中是否还有元素。 - 使用
remove()
将迭代器新近返回的元素删除。
迭代器不包含任何与它遍历的序列的类型信息。它能够将遍历序列与序列底层的结构进行分离。迭代器统一了对容器的访问方式。
ListIterator
是一个更强大的Iterator
的子类。它可以进行双向移动,以及获取当前及前后元素的索引等功能。
11.7 LinkedList
LinkedList除了上述所说的特性外,它还添加了可以使其用作栈/队列/双端队列的方法。
11.9 Set
Set不保存重复的元素。如果你试图将相同对象的多个实例添加到Set中,它会阻止这些重复现象(如何判断元素相同较为复杂)。Set中最常被使用的方法是测试鬼属性,正因如此,查找就成了Set中重要的操作。通常我们选择HashSet
,因为它对快速查找进行了优化。
Set与Collection拥有完全一样的接口,没有任何额外功能。其实Set就是Collection,只是行为不同(继承与多态思想的典型应用)
Set中的元素没有顺序可言,这是出于速度的考虑,HashSet使用了散列。HashSet所维护的顺序和TreeSet,LinkedHashSet都不同,因为他们的实现具有不同的元素储存方式。TreeSet(有顺序)将元素存储在红-黑树数据结构中,HashSet使用的是散列函数。LinkedHashSet因为查询速度的原因也使用了散列,但是它看起来使用了链表来维护元素 的插入顺序。
11.10 Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏。
Map与数组的其他Collection一样,可以很容易地拓展到多维,比如Map中的键对应的值为其他容器甚至是Map,因此,我们能够很容易地将容器组合起来从而快速生成强大的数据结构。
11.11 Queue
队列是一典型地先进先出容器。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,因为他们可以安全地将对象从一个任务传输给另一个任务。
上问提到,LinkedList方法支持队列的行为,并且实现了Queue接口,所以LinkedList可以用作Queue的一种实现。
先进先出描述了最典型的队列规则。优先级队列PriorityQueue
声明下一个弹出的是具有最高优先级的元素,可以通过提供自己的Comparator来修改这个顺序。
11.12 Collection和Iterator
标准的C++类库中,并没有容器的公共基类——容器之间的共性是通过迭代器来达到的。在Java中,迭代器与基类绑定到了一起,因为实现基类Collection意味着需要提供iterator()
方法,并且可以和底层容器的特定实现解耦。
11.13 Foreach与迭代器
Collection <String> cs=new LinkedList<String> ();
Collections.addAll(cs,"公,众,号,果,核,里,的,图,灵,可,带,劲,啦".split(","));
for(String s :cs){
System.out.println(s);
}
//输出:
公众号果核里的图灵可带劲啦
上述例子很简单,让我们来看一下。由于cs是Collection类型,所以既然cs能被foreach,那么说明所有的Collection对象都可以被foreach。
那么为什么能够工作?因为在JavaSE5之后,Collection实现了Iterable接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此,任何实现了Iterable的类,都可以将它用于foreach语句中。
11.14 总结
过时的Vector
、Hashtable
、Stack
不应再使用。
扫一扫,关注公众号
小白的成长探索之路。
网友评论