Container(Collection)

前言

关于集合和容器的概念,总是让人有些迷惑,在学习编程及工作2年之久的我,也没有对此有一个比较清晰的认识,这里打算对此话题将进行一个简单的探讨:

0.容器

容器的大致分类
Map:用来保存键值对.
Collection:用来保存单一元素 : 又分为 ListSetQueue(下图没有给出Queue的图示).
虚点框:接口,虚线框:抽象类,实线框:普通类,粗线框: 常用.
实线 :一个类可以生成指向的那个类.
虚线:接口、抽象类之间的继承.
带有空心箭头的点线:一个抽象类实现了一个接口,或者一个接口继承于另一个接口.
带有空心箭头的实线:一个具体的类实现了一个接口,或者一个类继承于另一个类.
实心箭头的点线:一个接口可以生成另一接口,即这个接口内部必然依赖,或者说继承于箭头所指向的那个接口.
Full Container Taxonomy

1.容器(集合)的概念

从编程语言 - 面向对象 - 容器(集合)
首先,先贴上几句引用自Thinking in Java(Forth Edition)一书的中的截取片段:

  • 所有编程语言都提供抽象机制。可以认为,人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。所谓的 “类型” 是指 “所抽象的是什么?” 汇编语言是对底层机器的轻微抽象。接着出现的许多所谓 “命令式” 语言 (如FORTRAN、BASIC、C等) 都是对汇编语言的抽象。这些语言在汇编语言基础上有了大幅的改进,但是它们所作的主要抽象仍要求在解决问题时要基于计算机的结构,而包时基于所要解决问题的结构来考虑。程序员必须建立起在机器模型 (位于 “解空间” 内,这是你对问题建模的地方,例如计算机 ) 和实际待解问题的模型 (位于 “问题空间” 内,这是问题存在的地方,例如一项业务) 之间的关联。建立这种映射是费力的,而且这不属于编程语言所固有的功能。
  • 面向对象方式通过向程序员提供表示问题空间中的元素的工具而更进了一步。我们将问题中空间中的元素及其在解空间中的表示称为 “对象”。(你还需要一些无法类比为问题空间元素的对象。) 这种思想的实质是: 程序可以通过添加新类型的对象使自身适用于某个特定问题,而不是根据运行解决方案的计算机来描述问题。 因此,当你在阅读描述解决方案的代码的同时,也是在阅读问题的表述。相比以前我们所使用的语言,这是一种更灵活和更强有力的语言抽象。
  • 在解决某个特定问题时需要多少个对象,或者它们将存活多久,以及如何存储这些对象,需要多少空间来创建这些对象。这类信息只有在运行时才能获得。在面向对象设计中的解决方案:创建另一种对象类型。这种新的对象类型持有对其他对象的引用。当然,你可以用在大多数语言中都有的数组类型来实现相同的功能。但是这个通常被称为容器 (也称为集合,不过Java类库以不同的含义使用 “集合” 这个术语,所以本书将使用 "容器 " 这个词) 的新对象,在任何需要时都可以扩充自己以容纳你置于其中的所有东西。因此不需要知道将来会把多少个对象置于容器中,只需要创建一个容器对象,然后让它处理所有细节。
  • 幸运的是,好的OOP语言都有一组容器,它们作为开发包的一部分。在Java的类库中,具有满足不同需要的各种类型的容器,例如 List (用于存储序列),Map (也被称为关联数组,用来建立对象之间的关联),Set (每种对象类型只持有一个),以及诸如队列、树、堆栈等更多的构件。
  • 从设计的观点来看,真正需要的只是一个可以被操作,从而解决问题的序列。如果单一类型的容器可以满足所有需求,那么就没有理由设计不同种类的序列了。然而还是需要对容器有所选择,这有两个原因。
  • 第一,不同容器提供了不同类型的接口和外部行为。堆栈相比于队列就具备不同的接口和行为,也不同于集合和列表的接口和行为。它们之中的某种容器提供的解决方案可能比其他容器要灵活得多。
  • 第二,不同的容器对于某些操作具有不同的效率。最好的例子就是两种List的比较: ArrayList和LinkedList。它们都是具有相同接口和外部行为的简单的序列,但是它们对某些操作所花费的代价却又天壤之别。在ArrayList中,随机访问元素是一个花费固定时间的操作;但是,对LinkedList来说,随机选取元素需要在列表中移动,这种代价是高昂的,访问越靠近表尾的元素,花费的时间越长。而另一方面,如果想在序列中间插入一个元素,LinkedList的开销却比ArrayList要小。上述操作以及其他操作的效率,依序列底层结构的不同而存在很大的差异。我们可以在一开始使用LinkedList构件程序,而在优化系统性能时该用ArrayList。接口List所带来的抽象,把容器之间进行转换时对代码产生的影响降到最小限度。

容器与集合的概念
在Thinking in Java中指明,容器和集合其实指的是一回事,即能够持有对其他对象的引用的对象类型。只不过在不同的书中,对它的称呼有所不同而已。

2.集合框架图简述

Java集合主要分为四种类型:

  • Set(集):集合中的对象没有重复的对象,并且不安特定方式排序;
  • List(列表):集合中的对象按照索引位置排序,可以有重复的对象;
  • Queue(队列):集合中的对象按照先进先出的规则来排序.可以有重复的对象;
  • Map(映射):集合中的每一个元素包含一对键(Key)对象和值(Value)对象,集合中没有重复的键对象,值对象可以重复;
  • 其中set接口与数学中的集合最接近,两者都不允许包含重复的元素。在Java API中,Collection接口表示集合,Set,List,和Queue都是Collection的子接口。而Map接口没有继承Collection接口。

Iterator与Collection

  • Iterator:虽然每个容器数据结构不同,但有其共性内容,集合的遍历,共性抽取,就是Iterartor接口,它隐藏了集合底层的数据结构,提供了遍历各种集合的统一入口。
    从集合框架图中可以看出,Collection接口依赖于Iterator接口。
 java.util 
Interface Collection<E>
All Superinterfaces: Iterable<E> 
All Known Subinterfaces

以上是API中的说明,可知Collection继承了Iterator接口,迭代器接口是集合接口的父接口,超级接口,实现类实现Collection时就必须实现Iterator接口。但是在jdk源码中:

public interface Collection<E> extends Iterable<E>
public interface Iterable<T> {
 
    /**
     * Returns an iterator over a set of elements of type T.
     * 
     * @return an Iterator.
     */
    Iterator<T> iterator();//只提供一个抽象方法,需要子类实现
}

Iterable接口在java.lang包中,Iterator属于java.util包。Collection在java.util包中,List、Queue、Set这三个接口也是在java.util包中。

List、Queue、Set接口继承了Collection接口,Collection接口继承了Iterable接口。即 List、Map、Set、Collection都继承了Iterable接口,这个接口定义了Iterator iterator()方法,iterator是集合中的一个重写的方法,实现Collection接口的所有子类都有iterator方法,返回一个实现了Iterator接口的实例对象。Iterator实例可以遍历集合。

  • 问题:为什么一定要实现Iterable接口,为什么不直接实现Iterator接口呢? (来自:http://liuyun025.iteye.com/blog/1321045 )

看一下JDK中的集合类,比如List一族或者Set一族,都是实现了Iterable接口,但并不直接实现Iterator接口。 仔细想一下这么做是有道理的。
因为Iterator接口的核心方法next()或者hasNext() 是依赖于迭代器的当前迭代位置的。

如果Collection直接实现Iterator接口,势必导致集合对象中包含当前迭代位置的数据(指针)。 当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。

除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。但即时这样,Collection也只能同时存在一个当前迭代位置。 而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器。 多个迭代器是互不干扰的。

  • 总结:^1

1.Iterable是Collection的父接口,Iterator是单独的接口,但是Iterable中有一个方法可以返回Iterator接口的实例

2.然后在Collection的子类中也有实现Iterator接口及其子接口的内部类,调用对应的方法可以返回Iterator对象

3.所有的Collection子类会实现Iteratable接口以实现foreach功能,Iteratable接口的实现又依赖于实现了Iterator的内部类(参照LinkedList中listIterator()和descendingIterator()的JDK源码)。有的容器类会有多个实现Iterator接口的内部类,通过返回不同的迭代器实现不同的迭代方式。

Collection与Map

虚线在UML建模的类图中表示依赖关系,由依赖的一方指向被依赖的一方。Map依赖Collection,它是Collection的一种实现。具体可以跟踪下jdk源码。
Java中Collection与Map的一些注意点 ^7

  • 1.Map 没有继承 Collection 接口。

  • 2.Collections是针对集合类的一个帮助类,Collection是一个接口

  • 3.Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。

  • 4.容器类仅能持有对象引用(指向对象的指针),而不是将对象信息copy一份至某位置。

  • 5.Map中元素,可以将key序列、value序列单独抽取出来。

使用keySet()抽取key序列,将map中的所有keys生成一个Set。

使用values()抽取value序列,将map中的所有values生成一个Collection。

为什么一个生成Set,一个生成Collection?那是因为,key总是独一无二的,value允许重复。

  • 6.Set和Collection拥有一模一样的接口,Set接口没有引入新方法,所以Set就是一个Collection,只不过其行为不同。
  • 7.map是接口,不能用new出对象。Hashmap是继承map接口的实现类,可以new出对象,HashMap是采用key的hashCode分组而实现的一种Map。 特点是查找速度快,缺点是不能保证迭代的顺序。

3.集合与数组

TIJ中有这样一句话:当然,你可以用在大多数语言中都有的数组类型来实现与集合相同的功能。那么集合与数组有什么区别呢?

  • 存储长度:
  • 数组的长度是固,改变数组长度的方式是创建新的数组,将旧数组复制到新的数组里。
  • 集合的长度的是可变的,可以根据元素的增加而自动增长,随元素减少而减小。
  • 存储内容:
  • 数组既可以存储基本数据类型(存储值),又可以存储引用数据类型(可以不同种对象,存储地址值)。
  • 集合只能存储引用数据类型(可以不同种对象,地址值),如果存储基本数据类型时,会自动装箱变成相应的包装类。

关于 集合与数组的比较,意义并不是很大,以后有机会再作讨论。

4.代码

具体代码细节参考jdk源码,这里不细究,或者 文末的参阅参考文献。
比如:ArrayList和其父类AbstractList都实现了List<>接口,效果上没什么特别用途,仅仅是为了让人阅读源码时知道子类实现了该核心接口。就像很多人都知道 ArrayList实现了List接口就够了,而不需要知道它继承AbstractList的相关细节。

参考文献

1.java集合Collection介绍和iterator方法-IQ等于猪.

2.java基础巩固系列(九)-Author:u010800530.

3.集合(Collection)与迭代器(Iterator).

4.Java集合笔记(一):Collection和Iterator接口.

5.java collection和Iterator.

6.java集合 之 Collection和Iterator接口.

7.Java中Collection与Map的一些注意点.



写在后面

文中如有侵权行为,请联系me。。。。。。。。。。。。。

发布了7 篇原创文章 · 获赞 0 · 访问量 193

猜你喜欢

转载自blog.csdn.net/qq_41765518/article/details/103868903