Java8集合遍历方式与Stream流

小tips:本文不会介绍所有遍历的方式,如果你并不明白什么是集合,那么推荐你去看我的入门文章:集合框架

Java8集合遍历方式与Stream流

Java8集合遍历方式

使用Iterator 接口操作集合

Iterator 接口是 Java 集合框架的成员,这个接口主要用于遍历Collection集合中的元素,所以也被称为迭代器。Iterator 必须依附于Collection对象,有一个Iterator 对象,必然就有一个Collection对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Cow {
public static void main(String[] args) {
Set books = new HashSet();
books.add("Java");
books.add("Java学习");
books.add("疯狂");

Iterator it = books.iterator();

while(it.hasNext()) {
String book = (String)it.next();
System.out.println(book);
if(book.equals("Java")) {

//使用Iterator迭代过程中,不可修改集合
books.remove(book); //这行代码会出错
}
}
}
}

当使用迭代器访问元素时,Collection集合里面地元素是不能通过remove来删除的,只能通过Iterator的remove才能删除,实际上当使用foreach来循环的时候也是同样的。而且在使用这两种循环的时候,你是不能在循环体内修改他的值。如果运行上面的代码会报

in thread "main" java.util.ConcurrentModificationException
1
2
3
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at test.Cow.main(Cow.java:17)

Iterator迭代器采用得是快速失败机制,一旦在迭代过程中检测到该集合已被修改(可能被程序中其他线程修改),程序立即引发 ConcurrentModificationException (也类似于上图的错误)

使用foreach循环遍历集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Cow {
public static void main(String[] args) {
Set books = new HashSet();
books.add("Java");
books.add("Java学习");
books.add("疯狂");

for(Object obj:books) {
String book = (String)obj;
System.out.println(book);
if(book.equals("Java")) {
books.remove(book);
}
}
System.out.println(books);
}
}

此段代码也会触发ConcurrentModificationException异常,foreach 循环中的迭代变量也不是集合元素本身, 系统只是依次把集合元素的值赋给迭代 变量, 因此在 foreach 循环中修改迭代变量的值也没有任何 实际意义。

上述两种方式也不总是会异常,当book.equals("Java")换成book.equals("疯狂")就不会引起错误,可能是因为需要判断的对象在遍历的末尾,当然这只是某种特殊情况,仍然不建议在使用这两种方式时修改集合

使用Predicate操作集合

Java8新增了一个removeIf( Predicate filter)方法,该方法可以批量删除符合filter条件的所有元素。Predicate也是函数式接口,因此可使用Lambda表达式作为参数。不过重点不在于删除,而是我们可以通过这个类来编写属于自己的统计方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

public class PredicateTest2 {
public static void main(String[] args) {
Set books = new HashSet();
books.add("我的天空是存在的想念,我的天空相连");
books.add("笔记本电脑");
books.add("小米显示器");
books.add("联想显示器");
books.add("虎扑显示器");
books.add("德玛西亚显示器");

//统计书名包含“疯狂”子串的图书数量
System.out.println(calAll(books,ele ->((String)ele).contains("显示器")));

//统计书名字符串长度大于10的图书数量
System.out.println(calAll(books,ele ->((String)ele).length()>10));

}

public static int calAll(Collection books,Predicate p) {
int total = 0;
for(Object obj : books) {

//使用Predicate的test()方法判断该对象是否满足Predicate指定的条件
if(p.test(obj)) {
total++;
}
}
return total;
}
}

使用Stream操作集合

Java8新增了Stream、IntStream、LongStream、DoubleStream等流式API。什么是Stream流呢?你假象一下,早上起床,是不是要先掀开被子-穿拖鞋-刷牙洗脸-开门去上班。这是一套早上起床流程,Stream流正是这样,你创建一个流,在每个节点都可以筛选或者改变一些东西,然后在最后结束这个流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.Set;
import java.util.stream.IntStream;

public class IntStreamTest {
public static void main(String[] args) {
IntStream is = IntStream.builder()
.add(20)
.add(13)
.add(-2)
.add(18)
.build();

//下面调用聚集方法的代码每次只能执行一次
System.out.println("is所有元素的最大值:"+ is.max().getAsInt());
System.out.println("is所有元素的最小值:"+is.min().getAsInt());
System.out.println("is所有元素的总和"+is.sum());
System.out.println("is所有元素的总数"+is.count());
System.out.println("is所有元素的平均值:"+is.average());
System.out.println("is所有元素的平方是否都大于20:"+is.allMatch(ele -> ele*ele >20));
System.out.println("is是否包含任何元素的平方大于20:"+is.anyMatch(ele -> ele*ele >20));

//将is映射城一个新Stream,新Stream的每个元素是元Stream的2倍+1
IntStream map = is.map(ele -> ele*2+1);

//使用方法引用的方式来遍历集合元素
map.forEach(System.out::println);

}
}

Stream流中有两种方法:

  • 中间方法:中间操作允许流保持打开状态,并允许调用流的后续方法,上述中map就是中间方法。更多详情方法请查询API文档。

    方法名 描述
    filter(Predicate predicate) 过滤Stream中所有不符合predicate的元素
    mapToXxx(ToXxxFunction mapper) Xxx是包装类,该方法返回的流中包含了ToXxxFunction转换生成的所有元素
    peek(Consumer action) 依次对每个元素执行一些操作,返回的流与原有的流包含相同的元素。该方法主要用于调试
    distinct() 用于排序流中所有重复元素。这是一个有状态的方法,比较是否相同是根据该元素的equals返回true
    sorted() 保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
    limit(long maxSize) 保证对该流的后续访问中最大允许方法的元素个数。这是一个有状态的、短路方法。
  • 末端方法:末端方法是对流的最终操作。当某个流执行末端方法后,这个流就会被消耗结束,不能继续执行后续方法。更多详情方法请查询API文档。

    方法名 描述
    forEach(Consumer action) 遍历流中所有元素,对每个元素执行action
    toArray() 将流中所有元素转换为一个数组
    reduce() 该方法有三个重载的版本,都用于通过某种操作来合并流中的元素
    min() 返回流中最小值
    max() 返回流中最大值
    count() 返回流中所有元素的数量
    anyMatch(Predicate predicate) 判断流中是否至少包含一个元素符合
    allMatch(Predicate predicate) 判断流中每个元素是否都符合Predicate
    noneMatch(Predicate predicate) 判断流中是否所有元素都不符合Predicate条件
    findFirst() 返回流中第一个元素
    findAny() 返回流中任意一个元素
    collect() 对流的元素执行可变的约简操作,把流输出为其他类型。一般用Collectors.toList()来返回集合,具体其他方法还挺复杂的。

流的方法还有如下两个特征

  • 有状态的方法:这种方法会给流增加一些新的状态,比如元素唯一,元素最大数量,元素排序

  • 短路方法:尽早结束对流的操作,不必检查所有的元素

使用流来改进之前我们自定义的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.HashSet;
import java.util.Set;

public class CollectionStream {
public static void main(String[] args) {
Set books = new HashSet();
books.add("我的天空是存在的想念,我的天空相连");
books.add("笔记本电脑");
books.add("小米显示器");
books.add("联想显示器");
books.add("虎扑显示器");
books.add("德玛西亚显示器");

//统计书名含有显示器的图书数量
System.out.println(books.stream().filter(ele -> ((String)ele).contains("显示器")).count());
}

}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!