Java集合
本章思维导图
Java集合概述
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。
集合和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量);而集合里只能保存对象(实际上只是保存对象的引用变量)。
Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
Set和List接口是Collection接口派生出的两个子接口,它们分别代表了无序集合和有序集合;Queue是java提供的队列实现,类似于List。
Map实现类用于保存具有映射关系的数据(关联数组);Map保存的每项数据都是key-value
对;Map里的key是不可重复的,key用于标识集合里的每项数据。
可以把Java所有集合分成三大类
Set
:把一个对象添加到Set集合时,Set集合无法记住添加这个元素的顺序,所以Set里的元素不能重复。List
:非常像一个数组,它可以记住每次添加元素的顺序、且List的长度可变。Map
:也像一个罐子,只是它里面的每项数据都由两个值组成。
Collection和Iterator接口
Collection体系图
Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可以用于操作Set集合,也可用于操作List和Queue集合。
Collection接口里定义了如下操作集合元素的方法:
boolean add(Object o)
:该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。boolean addAll(Collection c)
:该方法把集合c里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。void clear()
:清除集合里的所有元素,将集合长度变为0.boolean contains(Object o)
:返回集合里是否包含指定元素。boolean containsAll(Collection c)
:返回集合里是否包含集合c里的所有元素。boolean isEmpty()
:返回集合是否为空。当集合长度为0时返回true,否则返回false。Iterator iterator()
:返回一个Iterator对象,用于遍历集合里的元素。boolean remove(Object o)
:删除集合中指定元素o,当集合中包含了一个或多个元素o时,该方法只删除第一个符合条件的元素,该方法将返回true。boolean removeAll(Collection c)
:从集合中删除集合c里包含的所有元素(相当于调用该方法的集合减集合c),如果删除了一个或一个以上的元素,则该方法返回true。boolean retainAll(Collection c)
:从集合中删除集合c里不包含的元素(相当于把调用该方法的集合变成该集合和集合c的交集),如果该操作改变了调用该方法的集合,则该方法返回true。int size()
:该方法返回集合里元素的个数。Object[] toArray()
:该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
使用Lambda表达式遍历集合
Java8为Iterable接口新增了一个forEach(Consumer action)
默认方法,该方法所需参数的类型是一个函数式接口,而Iterable接口是Collection接口的父接口,因此Collection集合也可直接调用该方法。
当程序调用Iterable的forEach(Consumer action)
遍历集合时,程序会依次将集合元素传给Consumer的accept(T t)
方法(该接口中唯一的抽象方法)。正因为Consumer是函数式接口,因此可以使用Lambda表达式来遍历集合元素。
使用Java8增强的Iterator遍历集合元素
Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。
Iterator接口里定义了如下4个方法:
boolean hasNext()
:如果被迭代的集合元素还没有被遍历完,则返回true。Object next()
:返回集合里的下一个元素。void remove()
:删除集合里的上一次next方法返回的元素。void forEachRemainning(Consumer action)
:这是Java8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素。
Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。
当使用Iterato迭代访问Collection集合时,Collection集合里的元素不能被改变,只能通过Iterator的remove()
方法删除上一次next()
方法返回的集合元素才可以;否则将会引发java.util.ConcurrentModificationException
异常。
Iterator迭代器采用的是**快速失败(fail-fast)**机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发ConcurrentModificationException
异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
使用Lambda表达式遍历Iterator
Java8起为Iterator接口新增了一个forEachRemaining(Consumer action)
方法,该方法所需的Consumer参数同样也是函数式接口。当程序调用Iterator的forEachRemaining(Consumer action)
遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)
方法(该接口中唯一的抽象方法)。
使用foreach循环遍历集合元素
除可以使用Iterator接口迭代访问Collection集合里的元素之外,使用Java5提供的foreach循环迭代访问集合元素更加便捷。
与使用Ierator接口迭代访问集合元素类似的是,foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量。
同样,当使用foreach循环迭代访问集合元素时,该集合也不能被改变,否则将引发ConcurrentModificationException
异常。
使用Java8新增的Predicate操作集合
Java8起为Collection集合新增了一个removeIf(Predicate filter)
方法,该方法将会批量删除符合filter条件的所有元素。该方法需要一个Predicate谓词对象作为参数,Predicate也是函数式接口,因此可使用Lambda表达式作为参数。
使用Java8新增的Stream操作集合
Java8还新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。Stream是一个通用的流接口,而IntStream、LongStream、DoubleStream则代表元素为int、long、double的流。
Java8还为上面的每个流式API提供了对应的Builder,例如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder,开发者可以通过这些Builder来创建对应的流。
独立使用Stream的步骤如下:
- 使用Stream或XxxStream的
builder()
类方法创建该Stream对应Builder。 - 重复调用Builder的add()方法向该流中添加多个元素。
- 调用Builder的build()方法获取对应的Stream。
- 调用Stream的聚集方法。
Stream提供了大量的方法进行聚集操作,这些方法既可以是中间的(intermediate),也可以是末端的(terminal)。
中间方法:中间操作允许流保持打开状态,并允许直接调用后继方法。
末端方法:末端方法是对流的最终操作。当某个Stream执行末端方法后,该流将会被消耗且不可再用。
除此之外,关于流的方法好有如下两个特征:
- 有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销。
- 短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。
Stream常用的中间方法:
filter(Predicate predicate)
:过滤Stream中所有不符合predicate的元素。mapToXxx(ToXxxFunction mapper)
:使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回新流中包含了ToXxxFunction转换生成的所有元素。peek(Consumer action)
:依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。distinct()
:该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()
比较返回true)。这是一个有状态的方法。sorted()
:该方法用于保证流中的元素在后继的访问中处于有序状态。这是一个有状态的方法。limit(long maxSize)
:该方法用于保证对该流的后继访问中最大允许访问的元素个数。这是一个有状态的、短路方法。
Stream常用的末端方法:
forEach(Consumer action)
:遍历流中所有元素,对每个元素执行action。toArray()
:将流中所有元素转换为一个数组。reduce()
:该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。max()
:返回流中所有元素的最大值。count()
:返回流中所有元素的数量。anyMatch(Predicate predicate)
:判断流中是否至少包含一个元素符合Predicate条件。allMatch(Predicate predicate)
:判断流中是否每个元素都符合Predicate条件。noneMatch(Predicate predicate)
:判断流中是否所有元素都不符合Predicate条件。findFirst()
:返回流中的第一个元素。findAny()
:返回流中的任意一个元素。
Java8允许使用流式API来操作集合,Collection接口提供了一个stream()
默认方法,该方法返回该集合对应的流,接下来就即可通过流式API来操作集合元素。由于Stream可以对集合元素进行整体的聚集操作,因此Stream极大地丰富了集合的功能。
Set集合
Set集合与Collection基本相同,没有提供任何额外的方法。实际上Set就是Collection,只是行为略有不同(Set不允许包含重复元素)。
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。
HashSet类
HashSet是Set接口的典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet具有以下特点:
- 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
- HashSet是不同步的,如果多个线程同时访问一个HashSet,假设有两个或两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
- 集合元素值可以是null。
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()
方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。
如果两个元素通过equals()
方法比较返回true,但它们的hashCode()
方法返回值不相等,HashSet将会把它们存储到不同的位置,依然可以添加成功。
也就是说,HashSet集合判断两个元素相等的标准是两个对象通过equals()
方法比较相等,并且两个对象的hashCode()
方法返回值也相等。
当把一个对象放入HashSet中时,如果需要重写该对象对应类的equals()
方法,则也应该重写其hashCode()
方法。规则是:如果两个对象通过equals()
方法比较返回true,这两个对象的hashCode值也应该相同。
如果两个对象通过equals()
方法比较返回true,但这两个对象的hashCode()
方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合规则冲突了。
如果两个对象的hashCode()
方法返回相同的hashCode值,但它们通过equals()
方法比较返回false时将更麻烦:因为两个对象的hashCode值相同,HashSet将试图把他们保存到同一个位置,但又不行(否则将只剩下一个对象),所及实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。
HashSet中每个能存储元素的槽位(slot)通常称为桶(bucket),如果有多个元素的hashCode值相同,但它们通过equals()
方法比较返回false,就需要在一个“桶”里放多个元素,这样会导致性能下降。
重写hashCode()方法的基本规则:
- 在程序运行过程中,同一个对象多次调用
hashCode()
方法应该返回相同的值。 - 当两个对象通过
equals()
方法比较返回true时,这两个对象的has