本文是Marcus Biel的免费Java 8课程的一部分,该课程侧重于干净的代码原理。 在本文中,您将对Java Collections Framework(JCF)进行高级介绍。 不幸的是,集合是一个带有多个重载含义的词。 为了使事情更清晰,我们将首先预先讨论该词的含义。
<iframex height = 400 src="https://www.youtube.com/embed/4wbFRItSoYE" frameBorder = 0 width = 600 allowfullscreen ="> </iframex>
它的日常含义是"一组或一组事物"
它的日常含义是"一组或一组事物"
组成Java Collections Framework的接口和类的集合,
数据结构,例如盒子或容器,可以容纳一组对象,例如数组,
util.Collection 接口,这是两个主要JCF接口之一,或者
util.Collections ,一个实用程序类,可以帮助修改或操作Java集合。
本文基于《 OCA / OCP学习指南》的第11章,该书包含有关Java编程的知识。 作为作者Kathy Sierra和Bert Bates的忠实拥护者,即使您不打算成为一名合格的Java程序员,我也建议您阅读本书。
那么,从高级的角度来看,什么是Java Collections Framework? 首先,它实际上是通用接口和类的库或工具箱。 该工具箱包含各种集合接口和类,它们是数组的更强大的,面向对象的替代品。 与集合相关的实用程序接口和类也使更好的易用性。
总览
在本节中,我们将深入研究集合的接口和类层次结构。 与数组不同,所有集合的大小都可以动态增长或缩小。 正如我之前所说,集合包含对象组。 A Map可以将高度相关的对象对存储在一起,每对对象由a key和a value组成。 值不在地图中没有特定位置,但可以使用与其配对的键进行检索。 不用担心现在太多了,我们稍后将详细介绍。
图1显示了扩展或实现Collection接口的类和接口的层次结构-至少熟悉那里列出的名称将很有用。 Collection接口位于许多子接口和实现类的顶部。集合可以用提供Set,List和Queue的不同方式保存一组对象。集合定义为一组唯一的对象。唯一的定义是由其持有的对象类型的equals方法定义的。换句话说,集合不能容纳两个相等的对象。 A List被定义为对象序列。与集合相反,列表可以包含重复的条目。它还按插入的顺序保留其元素。队列有两个方面。将条目添加到其尾端,同时从顶部或头部删除条目。通常将其描述为"先进先出"(FIFO),这通常与现实生活中的排队很像,即排队的第一个人是第一个离开队列的人。
设置界面
HashSet,LinkedHasSet和TreeSet
HashSet,LinkedHashSet和TreeSet是Set的实现,位于图1中Collection接口层次结构的左端附近。HashSet是大多数情况下使用的默认实现。LinkedHashSet就像 HashSet和List的组合,因为它不允许像Set那样重复条目,但是像List那样按插入的顺序遍历其元素。TreeSet将不断保持 其所有元素都按某种排序的顺序排列。 但是请记住,没有免费的午餐之类的东西,并且每个增加的功能都需要一定的费用。
sortedSet和Navigable Set
看完实现Set的三个类之后,让我们看一下我们还没有讨论过的两个子接口。 顾名思义,sortedSet是一个Set,具有始终被排序的属性。 Java 6中添加的NavigableSet接口使我们能够浏览排序列表,提供检索大于或小于给定元素Set的下一个元素的方法。
列表界面
ArrayList和LinkedList
ArrayList是List的默认实现,位于图1的集合层次结构的中间。与any List实现一样,它确实允许重复元素和按插入顺序进行迭代。 由于它是基于数组的,因此迭代和读取的速度非常快,但是在随机位置添加或删除元素的速度非常慢,因为它必须重建底层的数组结构。 相反,LinkedList使得在列表中的任何位置添加或删除元素变得容易,而在随机位置读取则较慢。
在引导程序上
附带说明一下,我们不久将考虑Vector,它是自JDK 1起就存在的类,甚至早于Java 2添加的Collections Framework之前。总之,它的性能不是最佳的,因此没有新的代码。 应该曾经使用过。 ArrayList或LinkedList仅做得更好。
队列接口
最后,我们看一下实现Queue的类。 关于LinkedList还要提到的另一件事是,尽管它实现了List,但实际上它也实现了Queue。 这样做是基于以下事实:它的实际实现是一个双向链接列表,因此也很容易实现Queue接口。
PriorityQueue
<铅>
除了LinkedList,另一个常见的Queue实现是PriorityQueue。 它是一种自动保持其元素顺序的实现。 它具有与TreeSet类似的功能,不同之处在于它允许重复的条目。
地图界面
现在我们来看一下Map接口,奇怪的是它与Collection接口没有关系。 A Collection在一个实体上操作,而a Map在两个实体上操作:唯一密钥,例如 车辆识别号和与钥匙有关的物体,例如 一辆车。 要从a Map中检索对象,通常会使用其键。Map是许多接口和类的根,如图2所示。
hashtable,HashMap和LinkedHashMap
hashtable类是Java 1中第一个基于哈希表数据结构的Collection。 不幸的是,像Vector一样,该类由于性能欠佳而被弃用。 我们可以忘记它,而使用其他Map实现。HashMap是大多数情况下会使用的默认实现。
通常,A Map对其内部存储元素的方式不做任何保证。 但是,此规则的一个例外是LinkedHashMap,它允许我们按插入顺序迭代映射。
sortedMap
让我们看一下扩展Map的接口。 顾名思义,sortedMap扩展了Map并定义了一个连续排序的地图的协定。NavigableMap进一步扩展了它,增加了导航已排序地图的方法。 例如,这使我们可以使所有条目小于或大于某个条目。 实际上,Map和Set层次结构之间有许多相似之处。 原因是Set实现实际上在内部由Map实现支持。
地图界面
现在,让我们看一下地图界面。此接口与Collection接口无关。 A Collection在一个实体上运行,而地图在两个实体上运行-唯一键(例如,车辆识别号)和与该键相关的对象(例如,汽车对象)。借助键,您可以检索与之相关的对象。接口映射是许多接口和类的基础,我们现在将对其进行研究。类hashtable是Java JDK1中基于数据结构哈希表的第一个集合,因此Java创建者将其称为hashtable。不幸的是,这使得很难区分两者。像Vector一样,该类由于性能欠佳而被弃用,因此让我们删除它,然后将其遗忘。相反,请使用其他实现映射接口的类之一。HashMap是大多数情况下应使用的默认实现。地图通常不保证其内部存储元素的方式。该规则的一个例外是LinkedHashMap,它允许它按插入顺序迭代映射。最后但并非最不重要的是,TreeMap是一个不断排序的映射。
sortedMap
现在,让我们看一下扩展Map接口的接口。顾名思义,interface sortedMap扩展了Map接口并定义了一个连续排序的Map的协定。NavigableMap再次扩展了sortedMap interface并添加了在地图中导航的方法。例如,这允许您检索小于或大于给定条目的所有条目。实际上,Map和Set层次结构之间有许多相似之处。原因是Set实现实际上在内部由Map实现支持。最后但并非最不重要的一点是,您可能已经注意到Java Collection类通常包含其名称所基于的数据结构。要针对给定情况选择最佳集合,您必须先比较数据结构(如array,LinkedList,hashtable或Tree)的特定特征。简而言之,没有一个最佳选择,每个选择都有其自身的优缺点。我保证在以后的一集中谈论这个非常令人兴奋的话题。敬请关注。 Collection和Map类的概述仅向您展示了整个故事的一部分。在以后的文章中,我将向您介绍Java Collections Framework的并发容器。
大图
您可能已经注意到Java的Collection类通常包含基于其名称的数据结构。 要针对给定情况选择最佳集合,您必须比较和匹配数据结构(如LinkedList,hashtable或TreeSet)的属性与当前问题。 简而言之,没有一个最佳选择,因为每个选择都有其自身的优缺点。 实际上,这方面还有很多基础,因为此概述仅显示了Collection和Map类的巨大范围中的一小部分。 实际上,Java Collections Framework中甚至还有并发容器,这些容器用于并发编程。
泛型
泛型的主题至少与Java Collections Framework一样广泛。 在本文的上下文中,我们将仅讨论了解集合框架所需的最低要求。 在简要概述之后,有很多未解决的问题都是可以的。 一切都会一一解释。
1
| List<String> myList = new ArrayList<String>(100); |
注意尖括号的用法。 在左侧,我们用尖括号中的String参数定义了一个List变量myList。 我们告诉编译器myList变量只能引用某些包含字符串的列表。 然后,我们创建一个类型为ArrayList的对象,并再次告诉编译器该列表应仅包含字符串。 换句话说,这就是使容器具有类型安全性的原因。 另请注意,变量使用List类型而不是ArrayList。 这使我们的代码更加灵活。 您将只创建一次对象,但是您最终常常会在许多地方使用它。 话虽这么说,当您声明一个List而不是一个ArrayList时,您将后来用一个LinkedList替换了ArrayList,而您所要做的就是更改一行代码。
1
| Collection<String> myList = new ArrayList<String>(100); |
如果您确实不需要特定于List的方法,则也可以只使用Collection。最好始终使用最不具体,最小的接口作为变量。注意使用100作为ArrayList构造函数参数。在这种情况下,这是性能优化。由于ArrayList和所有基于hashtable的集合在内部在Arrays上进行操作,因此当此类集合的大小增大时,它将即时创建更大的数组,并将所有内容从旧数组传输到新数组。尽管这需要花费一些额外的时间,但现代硬件是如此之快,以至于通常这不是问题。另一方面,知道集合的确切大小或近似大小总比确定默认集合大小好。了解Java集合所基于的数据结构有助于更好地理解这种情况下的性能。注意如此小的细节通常是常规开发人员和软件工匠之间的区别。
1
| Map<VIN, Car> myMap = new HashMap<>(100); |
查看上面如何声明Map以及如何构造HashMap。映射是一个标识关键元素与一个值元素之间的关系,两者都可以是不同的类型。在上面的示例中,VIN(车辆识别号)用作键,而Car对象是值。类型参数以逗号分隔列表的形式添加在尖括号中。从Java 7开始,如果声明变量并在同一行中全部创建对象,则可以将第二对尖括号留空,因为编译器会从参考变量的泛型类型推断出对象的类型。空尖括号被称为菱形运算符,这是由于空尖括号如何形成菱形而得名。到目前为止,仅讨论了泛型类的用法,但是,我们锁定了要在实例化中使用的具体类型参数。仅当某些方法,界面或类被定义为预先以通用方式使用。
编写通用代码
清单1显示了一个通用定义的接口。 在第一行中,接口被定义为对两种泛型类型进行操作的接口,稍后必须指定这些泛型。 锁定这些类型后,将自动指定接口方法使用的类型。 如果在代码中看到一个字母的类型,则可能意味着可以通用的方式使用它。
1 2 3 4
| public interface MyInterface<E, T> {
E read();
void process(T o1, T o2);
} |
清单1
其他实用程序接口