Collections(操作集合的工具类)
该工具类里提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象1.设置为不可变、对集合对象实现同步控制等方法。自行看API即可。
2.有查找、替换集合元素的类方法。
有同步控制的方法,Collections类中提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
Java 中HashSet 、 TreeSet 、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则存在线程安全的问题。Collections提供了多个类方法可以把它们包装成线程同步的集合。
3.有设置不可变集合的方法。Java 9新增的不可变集合,增加了一个功能,以前假如要创建一个包含6个元素的Set集合,程序需要先创建Set集合,然后调用6次add()方法向Set集合中添加元素。Java 9对此进行了简化,程序直接调用Set、List、Map的of()方法即可创建包含N个元素的不可变集合,这样一行代码就可创建包含N个元素的集合。不可变意味着程序不能向集合中添加元素,也不能从集合中删除元素。
使用集合元素创建不可变集合,其中Set、List比较简单,程序只要为它们的of()方法传入N个集合元素即可创建Set、List集合。创建不可变的Map集合有两个方法: 使用of()方法时只要依次传入多个key-value对即可;还可使用ofEntries()方法,该方法可接受多个Entry对象,因此程序显式使用Map.entry()方法来创建Map.Entry对象。
Collection接口和Iterator接口
Java 11增强了Collection接口和Iterator接口,Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可操作Set集合,也可操作List和Queue集合。集合类就像容器,现实生活中容器的功能,无非就是添加对象、删除对象、清空容器、判断容器是否为空等,集合类就为这些功能提供了对应的方法。Collection接口里定义了如下操作集合元素的方法,自行查看API文档;
1.boolean add(Object o):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。
2.boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。
3.void clear():清除集合里的所有元素,将集合长度变为0。
4.boolean contains(Object o):返回集合里是否包含指定元素。
5.boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。
6.boolean isEmpty():返回集合是否为空。当集合长度为0时返回true,否则返回false。
7.Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素。
8.boolean remove(Object o):删除集合中的指定元素o,当集合中包含了一个或多个元素o时,该方法只删除第一个符合条件的元素,该方法将返回true。
9.boolean removeAll(Collection c):从集合中删除集合c里包 含的所有元素(相当于用调用该方法的集合减集合c),如果删除了一个或一个以上的元素,则该方法返回true。
10.boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用该方法的集合变成该集合和集合c的 交集),如果该操作改变了调用该方法的集合,则该方法返回true。
11.int size():该方法返回集合里元素的个数。
12.Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
涉及到泛型:
Java 11为Collection新增了一个toArray(IntFunction)方法,使用该方法的主要目的就是利用泛型。对于传统的toArray()方法而言,不管Collection本身是否使用泛型, toArray() 的 返回值总是Object[];但新增的toArray(IntFunction)方法不同,当Collection使用泛型时,toArray(IntFunction)可以返回特定类型的数组。toArray(IntFunction)方法的特点:
由于编译器推断strColl的类型为List(带泛型),因此该方法的返回值就是String[]类型。需要额外说明的是,由于使用该方法的主要目的就是利用泛型,因此toArray(IntFunction)方法参数通常就是它要返回的数组类型后面加双冒号和new(构造器引用)。
使用Lambda表达式遍历集合
Iterable接口是Collection接口的父接口,Java 8为Iterable接口新增了一个forEach(Consumer action)默认方法,该方法所需参数的类型是一个函数式接口,因此Collection集合也可直接调用该方法。
当程序调用Iterable的forEach(Consumer action)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。正因为Consumer是函数式接口,因此可以使用Lambda表达式来遍历集合元素。
上面程序中调用了Iterable的forEach()默认方法来遍历集合元素,传给该方法的参数是一个Lambda表达式,该Lambda表达式的目标类型是Consumer。forEach()方法会自动将集合元素逐个地传给Lambda表达式的形参,这样Lambda表达式的代码体即可遍历到集合元素了。
使用Iterator遍历集合元素
Iterator接口是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList,后面会再涉及。
Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。Iterator仅用于遍历集合,Iterator本 身并不提供盛装对象的能力。如果需要创建Iterator对象,则必须有 一个被迭代的集合。没有集合的Iterator仿佛无本之木,没有存在的价值。
Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。Iterator接口里定义了如下4个方法。
1.boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回true。
2.Object next():返回集合里的下一个元素。
3.void remove():删除集合里上一次next方法返回的元素。
4.void forEachRemaining(Consumer action),这是Java 8为Iterator新增的默认方法,该
方法可使用Lambda表达式来遍历集合元素。
注意一个结论:
当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以,否则将引发java.util.Concurrent ModificationException异常。
红框内代码位于Iterator迭代块内,也就是在Iterator迭代Collection集合过程中修改了Collection集合,所以程序将在运行时引发异常。
Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发ConcurrentModificationException异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
使用Lambda表达式遍历Iterator???垃圾,其实就是参数可以是lambda。。。
Java 8 为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Consumer参数同样也是函数式接口。当程序调用Iterator的forEachRemaining(Consumer action)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法(该接口中唯一的抽象方法)。
foreach循环遍历集合元素:
使用foreach循环遍历集合元素,上面不是说过?不一样,forEach循环就是。。。。for循环,前面说的是forEach()方法。。。。。。
除可使用Iterator接口迭代访问Collection集合里的元素之外, 使用Java 5提供的foreach循环迭代访问集合元素更加便捷。
使用Predicate操作集合
Java8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法将会批量删除符合filter条件的所有元素。该方法需要一个Predicate(谓词)对象作为参数,Predicate也是函数式接口,因此可使用Lambda表达式作为参数。
上面程序中粗体字代码调用了Collection集合的removeIf()方法批量删除集合中符合条件的元素,程序传入一个Lambda表达式作为过滤条件:所有长度小于10的字符串元素都会被删除。使用Predicate可以充分简化集合的运算,假设依然有上面程序所示的books集合,如果程序有如下三个统计需求:
1.统计书名中出现“疯狂”字符串的图书数量。
2.统计书名中出现“Java”字符串的图书数量。
3.统计书名长度大于10的图书数量。
此处只是一个假设,实际上还可能有更多的统计需求。如果采用传统的编程方式来完成这些需求,则需要执行三次循环,但采用Predicate只需要一个方法即可。如下程示范了这种用法。
上面程序先定义了一个calAll()方法,该方法将会使用Predicate判断每个集合元素是否符合特定条件,该条件将通过Predicate参数动态传入。从上面程序中三行粗体字代码可以看到,程序传入了三个Lambda表达式(其目标类型都是Predicate),这样calAll()方法就会统计满足Predicate条件的图书。
Stream操作集合(流)
Java 8新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。上面4 个接口中,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的聚集方法。
在上面4个步骤中,第4步可以根据具体需求来调用不同的方法,Stream提供了大量的聚集方法供用户调用,具体可参考Stream或XxxStream的API文档。对于大部分聚集方法而言,每个Stream只能执行一次。
创建了一个IntStream然后分别调用IntStream的聚集方法执行操作,这样即可获取该流的相关信息。注意:上面粗体字代码每次只能执行一行,因此需要把其他粗体字代码
注释掉。流过去就没了。。。。。
Stream提供了大量的方法进行聚集操作,这些方法既可以是“中间的”(intermediate),也可以是“末端的”(terminal)。
1.中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面程序中的map()方法就是中间方法。中间方法的返回值是另外一个流。下面简单介绍一下Stream常用的中间方法。
1.1filter(Predicate predicate):过滤Stream中所有不符合predicate的元素。
1.2.mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素。
1.3.peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。
1.4.distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。
1.5.sorted():该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
1.6.limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。
2.末端方法:
末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。上面程序中的sum()、count()、average()等方法都是末端方法。下面简单介绍一下Stream常用的末端方法。
2.1forEach(Consumer action):遍历流中所有元素,对每个元素执行action。
2.2toArray():将流中所有元素转换为一个数组。
2.3reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。
2.4min():返回流中所有元素的最小值。
2.5max():返回流中所有元素的最大值。
2.6count():返回流中所有元素的数量。
2.7anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件。
2.8allMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件。
2.9noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件。
2.10findFirst():返回流中的第一个元素。
2.11findAny():返回流中的任意一个元素。
除此之外,Java 8允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,该方法可返回该集合对应的流,接下来即可通过流式API来操作集合元素。由于Stream可以对集合元素进行整体的聚集操作,因此Stream极大地丰富了集合的功能。
例如之前介绍的示例程序,该程序需要额外定义一个calAll()方法来遍历集合元素,然后依次对每个集合元素进行判断——这太麻烦了。如果使用Stream,即可直接对集合中所有元素进行批量操作。下面使用Stream来改写这个程序。
从上面程序中粗体字代码可以看出,程序只要调用Collection的stream()方法即可返回该集合对应的Stream,接下来就可通过Stream提供的方法对所有集合元素进行处理,这样大大地简化了集合编程的代码,这也是Stream编程带来的优势。
最后的代码就是先调用Collection对象的stream()方法将集合转换为Stream对象,然后调用Stream对象的mapToInt()方法将其转换为IntStream,其中这个mapToInt()方法就是一 个中间方法,因此程序可继续调用IntStream的forEach()方法来遍历流中的元素。
除此之外,关于流的方法还有如下两个特征。
1.有状态的方法:这种方法会给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销。
2.短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。