本文章集干货满满,适用于长期处于“螺丝钉”岗位并渴望快速提升能力的 Java 工程师们。如果读者认为本文某些地方未讲解清楚或存在错误欢迎指正 👏
Java 的 Collection 体系
如果你是一个有工作经验的 Java 开发工程师,那么你日常一定经常使用 Java 所定义的 Collection( 集合 )。
什么是 Collection ?
Java 作为一个深度“解放”开发者的编程语言。它在设计之初就很贴心的为我们原生创造了“一篮子”的数据结构实现。并提供相应的工具方法。这个“篮子”就是Collection( 集合 )。
如何了解 Collection ?
如果你想了解 Collection,一个简单的方法就是打开 JDK 的 java.util.Collection
类。它的注释文档说明了如果你想了解 Collection 需要了解什么。
课代表来亲切的为大家画重点。
- Set - 不允许出现重复元素,并且无序的集合。
- List - 可以重复,并且是有序的集合。
- Map - 映射表,可以通过 key 快速查找 value。
- SortedSet - 一种有序的 Set 集合。
- SortedMap - 一种有序的 Map 集合。
- HashSet - Set 的实现类。
- TreeSet - Set 的实现类。 有序的Set集合。
- ArrayList - List 的实现累。容量动态增长的数组集合。
- Vector - 线程安全的 List 实现。( 但现在已被其他方案代替 )
- Collections - Collection 集合的工具类
- Arrays - 数组的工具类
- AbstractCollection - Collection 的骨架实现,用于多态。
这些是在 JDK 1.2
版本时提供的一些参考。下文我们将会对一些新的、常用且重要的集合类进行讲解。
几个在 Java 中的 De facto standard ( 业界标准 ) 其类似 Getter/Setter 方法。
XXX-s
- 在为某个类实现工具类方法时,我们通常在原有类名后加 ‘s’ 来标识。如:
- Collection -> Collections
- Array -> Arrays
- List -> Lists ( Guava 实现 )
- Map -> Maps ( Guava 实现 )
- …
AbstractXXX
- 在实现多态或一些类的骨架实现时,我们通常在该类前添加 ‘Abstract’ 来表示。如:
- AbstractCollection
- AbstractList
- …
Collection 体系的方法约定 ( 按重要性排序 )
如果你希望使用好 Collection ,并想了解其"脾气秉性"。你可能需要知道 Collection 的一些方法约定。
equals
这是大家熟悉且陌生的一个方法。熟悉是指它是来自 Java “对象之祖” 的 Object
。 陌生是指你并不经常用到它。
但是它却是一个很重要的方法。重要到 JDK
列举了四条特性来讲解该方法。
特性一 自反性 ( reflexive )
一个非空的对象,它一定和自己是相等的 (
x.equals(x) == true
)
特性二 对称性 ( symmetric )
如果对象 A 等于对象 B,那么对象 B 也一定要等于对象 A。(
( a.equals(b) && b.euals(a) ) == true
)
特性三 传递性 ( transitive )
如果对象 A 等于对象 B,对象 B 等于对象 C,那么对象 C 一定等于对象 A。 (
( a.equals(b) && b.equals(c) && c.equals(a) ) == true
)
特性四 一致性 ( consistent )
如果对象 A 等于对象 B,那么无论做了多少操作。A 都要等于 B。
这些特性和数学上对于等于的定义基本是一致的。
hashCode
来自 Object
的第二个方法约定。如果你不遵守该约定,那么你在日常工作中经常会遇到一些奇奇怪怪的问题。
- 一个对象无论经过多少操作其 hashcode 值必须始终唯一。
- 如果对象 A 和对象 B 调用
equals
方法相等,那么它们调用hashcode
方法也一定返回相同值。 - 如果对象 A 和对象 B 调用
equals
方法不相等,允许它们调用hashcode
方法返回的值相等。但是不推荐这样做,这样会影响内部 Hash 表的查找速度。我们将会在讲解 HashMap 时进行重点讲解。
该方法约定就会产生了一个 De facto standard ( 业界标准 ),那就是当你重写
equals
方法时也需要同时重写hashcode
方法。( 根据 2 原则 )。这也解释了 IntelliJ IDEA 快捷方法中要把这两个方法的重写放在一起。
Comparable/Comparator
一个接口,用来声明该类可以用来比较。在调用 Collections.sort()
方法,或者在对集合内的对象进行排序时,就需要使得被排序的类实现该接口。 其默认按照自然顺序从小到大排序。
X < Y => -1
X = Y => 0
X > Y => 1
现在的对象中基本已经实现基本类型的 Comparable 接口,所以大部分已经不需要手写了。例如:Long.compare()/Long.compareTo()
方法等。
对于实现了 Comparable 接口的类。如果它们的
compareTo
方法返回相等,那么它们的equals
方法也必须要相等。否则,你就会发现你的对象消失了。我们将会在讲解 Set 的时候来详细讲解这个问题。
迭代器 ( Iterator / Iterable )
在没有迭代器的日子里,我们如果需要遍历集合怎么办? 我们拿 HashSet 和 ArrayList 举例。
ArrayList 在内存中的展示
伪代码
for (i = 0, i < list.size(),i++){
value = list[i]
do somethings...
}
HashSet 在内存中的展示
伪代码
// hashtable
for (i = 0,i < hashset.size(),i++){
// 链表
for(i = 0, i < hashset.getLink(),i++){
do somethings...
}
}
我们不需要关注上面的实现是不是能过编译器,只需要关注这两种不同的集合其实现方法不同。以后的文章中将会深入讲解各个集合。
当多个集合的时候可能遍历元素的方法都各不相同。这时我们就需要一个统一的方法来抽象 Collection
集合的遍历。
此时,Iterator 就闪亮登场了。
Iterator 接口定义了 hasNext
和 next
方法来解决这个问题。
- hasNext - 用来判断是否还拥有下一个元素。
- next - 获取下一个元素。
有了这个接口,我们就可以使用 xx.forEach
、for(A a:lista)
等语法糖来简便操作了。
语法糖:是指为计算机语言中添加一种简便的语法,来实现其原本复杂的功能。
迭代器的 fail-fast 机制
如果你去面试,就可能会被问到什么是 fail-fast。那么它到底是什么呢,ArrayList 的源码文档中就有提及。
fail-fast 是你在使用一个迭代器的时候对该迭代器的集合元素进行操作时,会得到一个 ConcurrentModificationException
异常,其目的是保证在你做一个错误的操作时快速的抛出异常。 而不是带着错误的结果继续进行运算。
回头看看( 总结 )
本篇文章为大家讲解了 Collection
体系中一些核心的约定,例如 equals
、hashcode
等。与一些常见的知识与问题,例如 "重写 equals
必须重写 hashcode
"、"什么是 Iterator 的 fail-fast 机制"等。 在接下来对每个集合进行讲解的时候我们将会反复的用到本文所提到的知识。希望大家可以深刻理解~