概述
- Java集合大致可分为Set、List、Queue和Map四种体系,其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map代表具有映射关系的集合;Java5又增加了Queue集合体系,代表一种队列集合实现。
- Java集合就像一个容器,可以把多个对象(实际上是对象的引用,但习惯上都称对象)“丢进”该容器中。
- 集合体系如下:
Iterator
- Iterator接口也是java集合框架中的成员,它并不是作为容器,而是主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
- Iterator接口隐藏了各种Collection实现类的底层细节,向外界提供了遍历Collection集合的统一编程接口。
简单使用
1 | public class IteratorTest { |
输出结果:1
0 1 2 3 4 [0, 2, 3, 4]
注意
- 当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
- 当使用Iterator变量元素时,Collection集合的元素不能被修改,除了通过Iterator的remove方法删除上一次next方法返回的元素,否则会引发java.util.ConcurrentModificationException异常。例如下面操作:
1
2
3if (i == 1) {
list.remove(i); //该操作会引发异常
}
这是因为Iterator采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即抛出异常,这样可以避免共享资源引发的潜在问题。
Predicate
- Java8为Collection集合新增了一个removeIf(Predicate<? super E> filter)方法,该方法将会批量删除符合条件(Predicate接口中的test方法返回结果为true)的所有元素
基本使用
1 | public class PredicateTest { |
输出结果:1
[0, 1, 3, 4]
由于Predicate也是函数式接口,因此可以使用Lambda表达式作为参数,化简后如下1
list.removeIf(integer -> integer == 2); //删除值为2的元素
Stream
- Java8还新增了Stream、IntStream、LongStream、DoubleStream等流式接口,Stream是一个通用的流接口,而IntStream、LongStream、DoubleStream分别代表int、long、double元素的流。
简单使用
1 | public class StreamTest { |
输出结果:1
所以元素的最大值为:4
如果注释max,取消注释map的话1
0 2 4 6 8
Stream的方法
- Stream提供了大量的方法进行聚集操作,这些方法既可以是“中间的”,也可以是“末端的”。中间方法允许流保持打开状态,并允许直接调用后续方法,例如map方法。而末端方法是对流的最终操作,执行完该方法后流将会被“消耗”而不可再用,例如max和min方法。
常用的中间方法
常用的末端方法
Set
HashSet
- HashSet是Set接口的典型实现,HashSet按哈希算法来存储集合中的元素,因此具有很好的存取和查找功能。
- HashSet具有以下特点:
- 不能保证元素的排列顺序
- HashSet不能同步的,如果有多个线程同时访问一个HashSet对象,必须通过代码来保证其同步
- 集合元素值可以是null(但只能有一个)
- 当向HashSet集合中存入一个元素时,HashSet会调用对象的hashCode()方法来得到该对象的哈希值,然后根据该哈希值决定该对象在HashSet中的存储位置。即使两个元素通过equals()方法比较返回true,只要它们的哈希值不一样,HashSet还是会把它们放在不同位置,依然可以添加成功。
- HashSet是底层是基于HashMap来实现的,所以HashMap适用的HashSet也基本适用,反之亦然。
注意
- 当重写一个类的equals方法或hashCode方法时,应该尽量保证两个对象通过equals方法比较返回true时,它们的hashCode方法返回值也相等。原因如下:
HashSet(或者HashMap)判断两个元素相等的标志是两个对象通过equals方法比较返回true,并且它们的hashCode方法返回值也相等。如果两个对象通过equals方法比较返回true,但这两个对象返回的哈希值不同,那么这两个对象都可以添加成功,这违背了Set集合的规则。如果通过equals方法比较返回false,但两个对象的哈希值相同,那么这两个对象都能添加成功,这部违背Set集合的规则,但是由于两个对象的哈希值相同,HashSet试图将他们保存在同一位置,但这样又不行,所以实际上会在这个位置用链式结构来保持多个对象,这将会导致性能下降。
- 当程序把可变对象添加到HashSet之后,尽量不要去修改该对象中参与计算hashCode、equals的实例变量,否则将会导致HashSet无法正确操作这些集合元素。
LinkedHashSet
- LinkedHashSet是HashSet的子类,LinkedHashSet也是根据元素的哈希值来决定元素的存储位置,但它同时使用链表来维护元素的次序,也就是说当遍历LinkedHashSet的元素时,将会按照元素的添加顺序来遍历。
- LinkedHashSet需要维护元素的顺序,因此性能略低于HashSet,但在迭代访问Set集合的全部元素时有很好的性能。
TreeSet
- TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态。
- 与HashSet集合相比,TreeSet还提供了几个额外的方法,例如访问第一个、最后一个、前一个、后一个或者截取子TreeSet的方法。
- TreeSet采用红黑树的数据结构来存储集合元素。
- TreeSet支持两种排序:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
基本使用
1 | public class TreeSetTest { |
输出结果:1
2
3
4
5
6[-7, 2, 5, 10]
-7
10
[-7, 2]
[5, 10]
[-7, 2]
自然排序
- TreeSet会调用集合元素的compareTo方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。
java提供了一个Comparable接口,该接口里定义了一个compareTo(T o)方法,该方法返回一个整数值。当一个对象调用该方法与另一个对象进行比较时,例如obj1.compareTo(obj2);如果该方法返回0,则表示这两个对象相等;如果返回一个正整数,则表示obj1大于obj2;如果返回一个负整数,则表示obj1小于obj2。
- 如果试图把一个对象添加到TreeSet,那么该对象的类必须实现Comparable接口,否则程序将抛出ClassCastException异常
- 当把一个对象加入TreeSet集合时,TreeSet调用该对象的compareTo方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo方法比较返回0,则表示这两个对象相等,新对象无法添加到集合中。
定制排序
- 如果需要实现定制排序,例如降序排列,则可以通过Comparator接口(注意和上面的Comparable接口区别开)的compare(T o1, T o2)来实现,该方法用于比较o1和o2的大小:如果该方法返回正整数,则表明o1大于o2;如果该方法返回0,则表明o1等于o2;如果该方法返回负整数,则表明o1小于o2。
例子
1 | public class TreeSetTest2 { |
输出结果:1
[10, 5, 2, -7]
注意
- 当需要把一个对象加入TreeSet中,如果重写该对象对应类的equals方法,必须保证该方法与compareTo方法返回的结果一致。其规则是:如果这两个对象通过equals方法比较返回true时,通过compareTo方法比较应该返回0。
- 不要修改集合元素的关键实例变量(涉及到比较元素是否相等的实例变量),否则将会导致对应元素无法删除,并且可能破坏了集合元素的有序性。
EnumSet
- EnumSet是专门为枚举类设计的集合类,EnumSet中的所有元素必须是同一个枚举类中的枚举元素。
- EnumSet的集合元素也是有序的,其以枚举值在其枚举类中定义的顺序来排序。
- EnumSet在内部以位相量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。尤其是进行批量操作(如containsAll()、retainAll()方法)时,如果其参数也是EnumSet集合,则该批量操作的执行速度也非常快。
- EnumSet不允许加入null元素,否则将会抛出java.lang.NullPointerException异常。
简单使用
1 | public class EnumSetTest { |
输出结果:1
2
3
4
5
6
7[SPRING, SUMMER, FAIL, WINTER]
[]
[SPRING]
[SUMMER, WINTER]
[SUMMER, FAIL, WINTER]
[SPRING]
[SPRING]
各Set实现类的性能分析
- HashSet和TreeSet是Set的两个典型实现,HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护元素的次序。只有当需要一个保存顺序的Set时,才考虑使用TreeSet,否则都应该使用HashSet。
- 对于普通的插入、删除操作,LinkedHashSet比HashSet要慢一些,这是由于维护链表所带来的额外开销,但由于有了链表,遍历LinkedHashSet会更快。
- EnumSet是所有Set实现类中性能最好的,但它只能保持同一个枚举类的枚举值。
- 这几个实现类都是线程不安全的,如果有多个线程同时访问并修改一个Set集合,则必须手动保证该Set集合的同步性。