最近经常遇到问题:要获取到集合中某一属性值重复的数据,除了for 循环,还有更简单得处理方式?

先来引入 Stream 流的概念。

Stream 阐述

Stream API(java.util.stream.*) 是 Java 8 中新增重要特性。
Stream 将要处理的元素集合看作一种流,由于java.util.stream.Stream 是一个 Interface ,在其中提供了函数方法,
使流在管道中进行一系列处理(如过滤,映射,聚合等)后生成的结果集合,类似于在数据库执行 SQL 语句。
这个过程通常不会对数据源造成影响。

1
2
3
4
5
6
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println); // C1 C2

在以上示例中, 创建 Stream 流,filter,map 和 sorted 是中间操作,而 forEach 是一个终端操作。Stream操作链称为操作管道。

Stream 用法

可以从各种数据源中创建 Stream 流,其中以 Collection 集合最为常见。
如 List 和 Set 均支持 stream() 方法来创建顺序流 stream() 或者是并行流 parallelStream()。

常用创建 Stream 流方法

1.使用 Collection 下的 stream() 和 parallelStream() 方法来创建 Stream

1
2
3
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

2.Arrays 提供了创建流的静态方法 stream(),将数组转成流
1
2
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);

3.使用 Stream 中的静态方法:of()、iterate()、generate()
1
2
3
4
5
6
7
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10

Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);

4.使用 Pattern.splitAsStream() 方法,将字符串分隔成流
1
2
3
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);

5.一些类也提供了创建流的方法:
1
2
3
IntStream.range(start, stop);
BufferedReader.lines();
Random.ints();

中间操作 Stream 流

1.filter:用于通过设置的条件过滤出元素,其中java.util.Objects提供了空元素的过滤

2.映射 map 方法用于映射每个元素到对应的结果

3.排序 sorted:使用 java.util.Comparator或者 reversed 更方便的对流进行升降排序

4.distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素

5.skip(n):跳过n元素,配合limit(n)可实现分页

6.limit(n):用于获取指定数量的流…

终端操作 Stream 流

1.forEach() 迭代流中的每个元素,java.util.function.Consumer接受参数没有返回值

1
Stream.of(1,2,3,4,5).forEach(System.out::println);

2.collect() 可用于返回列表或字符串,java.util.Collectors 类中有求和、计算均值、取最值、字符串连接等多种收集方法。
1
2
3
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> count = strings.stream().filter(string -> string.isEmpty()).collect(Collectors.toList());
System.out.println(count.size()); // 2

3.reduce 用于对stream中元素进行聚合求值,操作函数 accumulator 接受两个参数x,y返回r

4.anyMatch()、allMatch()、noneMatch() 接收参数Predicate,返回boolean。

5.findFirst()、findAny()、count()、max()、min() …

使用 Stream 流解决集合中数据重复问题

我们以 Employee 为实体,对比 获取重复code值的 写法:

Employee 实体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Employee extends Model<Employee> {
@ApiModelProperty(value = "ID")
@TableField("ID")
private Long id;

@ApiModelProperty(value = "编码")
@TableId("CODE")
private String code;

@ApiModelProperty(value = "姓名")
@TableField("NAME")
private String name;

@ApiModelProperty(value = "年龄")
@TableField("AGE")
private Integer age;
...
}

for 写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> duplicate_code = new ArrayList<>();
List<Employee> employeeList = fromDB();
if (employeeList.size() > 0) {
for (int i = 0; i < employeeList.size(); i++) {
Employee employee = employeeList.get(i);
for (Employee entity : employeeList) {
String code = entity.getCode();
if (StringUtils.isBlank(code) && StringUtils.isBlank(employee.getCode()) && code.equals(employee.getCode())) {
duplicate_code.add(code);
}
}
}
}

Stream 写法:

1
2
3
4
5
6
7
List<String> duplicate_code = new ArrayList<>();
List<Employee> employeeList = fromDB();
Map<Object, Long> map = employeeList.stream().collect(Collectors.groupingBy(employee -> employee.getCode(), Collectors.counting()));
Stream<Object> stringStream = map.entrySet().stream().filter(entry -> entry.getValue() > 1).map(entry -> entry.getKey());
stringStream.forEach(str -> {
duplicate_code.add(String.valueOf(str));
});

由此可见,使用Java 8 的 Stream 流方式获取到集合中某一属性值重复数据的问题更方便、简洁!

了解更多有关 Java8 Stream 流的相关信息,请参考 Stream Javadoc 阅读官方文档