Stream流操作

Java使用的Srteam流

Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

lambda

Java 常见Stream操作

Java8的Stream使用技巧归纳

一、双冒号“::”就是 Java 中的方法引用(Method references)

DR2ocTyanHMLr1i

场景

场景一:从String读取int[]

1
2
3
4
5
Scanner scanner = new Scanner(System.in);
// 此处直接nextLine读取,因为nextInt读取后,需要再nextLine读取换行符
int n = Integer.parseInt(scanner.nextLine());
// 此处先将字符串分割成String数组,然后转成IntStream,然后到int数组
int[] nums = Arrays.stream(scanner.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray();

场景二:int[]放入Set中去重复,需要int[]转成List

1
2
3
4
5
6
// 首先要知道Set去重复,需要放入List<Integer>中,所以需要int[]转List<Integer>
int[] nums = new int[]{1, 2, 3, 4};
int[] temp = nums.clone(); // 需要进行深拷贝,在回溯中用的比较多
// 注意,这里的boxed装箱,用的是mapToObj(Integer::valueOf)
// 是int[]转Stream<Integer>,再转List<Integer>,注意与场景一的区别,一个是IntStream一个是Stream<Integer>
List<Integer> list = Arrays.stream(temp).boxed().collect(Collectors.toList());

场景三:int[]里面元素去重复

1
int[] b = Arrays.stream(scanner.nextLine().split(" ")).mapToInt(Integer::valueOf).distinct().toArray();

场景四:元素比较,自定义cmp,需要包装类

1
2
3
4
5
6
7
int[] nums = new int[]{1, 34, 6, 2, 45, 14, 9};
// 注意,先转成Stream<Integer>,再转到Integer[]
Integer[] integers = Arrays.stream(nums).boxed().toList().toArray(new Integer[0]);
// 注意此处需要Integer[]类型,int[]就不行
// 而如果给int[][]排序,那么后面cmp是可以的,因为二维的话,后面放入的一维算对象?
Arrays.sort(integers, (a, b) -> b - a);
Arrays.stream(integers).forEach(System.out::println);

场景五:数组中的最大值、求和、等等

1
2
3
int[] dp = new int[5];
int ans = Arrays.stream(dp).max().getAsInt();
int ans1 = Arrays.stream(dp).sum();

场景六:排序

1
2
3
4
5
6
7
8
9
10
11
12
13
//升序(如果想要降序,将上面compareTo对比换个位置即可)
List<User> collect = users.stream()
.sorted()
.collect(Collectors.toList());
//使用自定义的排序方法
List<User> collect1 = users.stream()
.sorted((u1, u2) -> u1.getAge() - u2.getAge())
.collect(Collectors.toList());

//使用Comparator里面的方法
List<User> collect2 = users.stream()
.sorted(Comparator.comparingInt(User::getAge).reversed())
.collect(Collectors.toList());

场景六:离散化

treeset去重排序 + map重新编号

1
2
3
4
5
6
7
8
9
10
11
12
13
// 未验证
// 离散化,1 treeset排序去重
// Set<Integer> set = Arrays.stream(nums).boxed().collect(Collectors.toCollection(TreeSet::new));
Set<Integer> set = Arrays.stream(nums).boxed().collect(Collectors.toCollection(() -> new TreeSet<>((a, b) -> b - a)));
// 2 map编号,这里没有用stream是因为,编号递增不能放入lambda表达式,因为他需要final的量
int index = 0;
Map<Integer, Integer> map = new HashMap<>();
for (Integer num : set) {
map.put(num, index++);
}

// 3 然后根据编号,反映给原始数组nums,此处完整的lambda表达式为 num -> { return map.get(num);}
nums = Arrays.stream(nums).map(map::get).toArray();

一些用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String[] strs = new String[]{"one", "two", "three", "four"};
List<String> collect = Arrays.stream(strs)
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());

// 注意,ForEach是消费Stream的,是Terimal的,所以ForEach之后不能collect,而使用peek可以,不消费Stream
// 一下是输出
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

reduce

reduce这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
下边是使用reduce的一些常见案例:

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
36
37
38
39
40
41
42
43
44
45
46
47
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);

//上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。

// 当然,如果基本类型,可以直接像下面这样
int[] ints = {1,2,3,4,5};
int sum = Arrays.stream(ints).sum();
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12,"男"));
users.add(new User("jack",14,"男"));
users.add(new User("tom",16,"男"));

//计算年龄均值
double avg = users.stream()
.mapToInt(User::getAge)
.average()
.getAsDouble();

ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//年龄最大的user
User maxuser = users.stream()
.max(Comparator.comparingInt(User::getAge))
.get();
//年龄最小的user
User minuser = users.stream()
.min(Comparator.comparingInt(User::getAge))
.get();

ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
long count = users.stream().count();

其他方法

distinct去重、sorted、limit只要个数、skip去掉个数

转Map

1
2
3
4
// 先将LCS转换为LIS的前序工作做好
Map<Integer, Integer> map = IntStream.range(0, target.length).boxed()
.collect(Collectors.toMap(index -> target[index], Function.identity(), (a, b) -> a, HashMap::new));
int[] indexNums = Arrays.stream(arr).filter(map::containsKey).map(map::get).toArray();

1636. 按照频率将数组升序排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int[] freqSort(int[] nums) {
// 统计nums中的数字的频率,元素为key,频率为value
Map<Integer, Integer> map = Arrays.stream(nums).boxed()
.collect(Collectors.toMap(Function.identity(), temp -> 1, (val1, val2) -> val1 + val2, HashMap::new));

// 根据map中的value和key排序,但是只能放到list中,因为map无序
List<int[]> collect = map.entrySet().stream().sorted((a, b) -> {
if (a.getValue().equals(b.getValue())) {
return b.getKey() - a.getKey();
}

return a.getValue() - b.getValue();
}).map(a -> new int[]{a.getKey(), a.getValue()}).collect(Collectors.toList());

// .map不能一对多,只能曲线救国
int index = 0;
for (int[] ints : collect) {
while (ints[1]-- > 0) { nums[index++] = ints[0]; }
}

return nums;
}

Author: Jcwang

Permalink: http://example.com/2022/08/19/Stream%E6%B5%81%E6%93%8D%E4%BD%9C/