Java:Stream函数式编程

久闻Java8 Stream API大名,在Youtube上看了Venkat关于它的一个讲座,初步体会到了它的魅力;之后打算把《Efficient Java》那一章也看了,但是不知道怎么的,看自闭了,直接颓废了一天半555555,后面有机会再二刷吧

函数式编程就是将一个大的问题拆分成一小步一小步,每一步的职责分明,函数是一等公民,可以作为参数;在JavaScript中进行函数式编程非常方便,es6中对数组处理 mapreduce, foreach等函数,而且可以方便地将函数作为参数传入到另一个函数中,也可以作为函数的返回值,非常经典的就是callback了;使用过前端工程化工具gulp 之后就觉得它的pipeline非常地优雅,使用第三方插件只需要把它封装的函数写在 pipe()函数中就可以了,一连串 pipe 下来最后得到最终结果;在java8中对这些特性做到了很好的支持


问题一:求素数

普通的做法就是写一个for循环

1
2
3
4
5
6
7
8
private static boolean isPrime(final int data) {
for (int i = 2; i < data; i++) {
if (data % i == 0) {
return false;
}
}
return data > 1;
}

这里,那个for循环和需要解决的问题几乎没关系,但是却占去了大量的空间。Venkat说,代码应该是可以让别人一目了然的,是poem,而不是puzzle;而且,比较一下 Stream 的第一种写法

1
2
3
4
5
private static boolean isPrime(final int data) {
return data > 1 &&
IntStream.range(2, data)
.noneMatch(index -> data % index == 0);
}

使用 IntStream.range(2, data) 代替for循环,使用 noneMatch 让代码意图让人一目了然;但是 noneMatch 中的代码还是不能让人一眼就看出意图;那就在封装一层呗,将其作为参数传入

1
2
3
4
5
6
private static boolean isPrime(final int data) {
Predicate<Integer> isDivisible = divistor -> data % divistor == 0;
return data > 1 &&
IntStream.range(2, data)
.noneMatch(isDivisible::test);
}

虽然没有上一个版本简洁,但是可读性强了一些


问题二:求整数List中第一个大于3的偶数的两倍值

1
2
3
4
5
6
7
8
9
10
private static int task(List<Integer> numbers) {
int result = 0;
for (int number : numbers) {
if (number > 3 && number % 2 == 0) {
result = number*2;
break;
}
}
return result;
}

那如何用stream api采用函数式编程方式编写呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 函数所在类名为 Main;如果不存在则返回 0
private static int task(List<Integer> numbers) {
return numbers.stream()
// 条件1 大于3
.filter(isGreaterThan(3))
// 条件2 为偶数
.filter(Main::isEven)
// 条件3 第一个
.findFirst()
// 条件4 两倍
.map(e -> e * 2)
.orElse(0);
}

private static boolean isEven(Integer integer) {
return integer % 2 == 0;
}

private static Predicate<Integer> isGreaterThan(int pivot) {
return number -> number > pivot;
}

问题三:整数List求和,可设置筛选条件

普通的解法这里就不写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
// sum || condition sum
List<Integer> value = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 无条件
System.out.println(totalValue(value, v -> true));

// 求所有偶数和
System.out.println(totalValue(value, Main::isEven));
}

public static int totalValue(List<Integer> integers, Predicate<Integer> selector) {
return integers.stream()
.filter(selector)
.reduce(0, Math::addExact);
}
private static boolean isEven(Integer integer) {
return integer % 2 == 0;
}

问题四:对文本文件数据进行处理

文本文件如下

1
2
3
4
5
AA,aaa,2312,423,adas
BB,dad,rwqr,fs,FDSFDS
DSAFSD,FDSFDSA,EWR,WE
aa
BB,dsafds,fsd

将其存在Map中,每行以逗号为分割符,第一个字符作为key,value为分割好的字符数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws Exception{

// 返回值为函数
Function<String, Function<String, String[]>> spiltString = spliter -> str -> str.split(spliter);

// 读取文件
Stream<String> lines = Files.lines(Paths.get("txt.txt"));
lines.map(spiltString.apply(","))
// 拆分成Map
.collect(Collectors.toMap(
x -> Integer.parseInt(x[0]),
x -> x
))
// 打印结果
.forEach((k, v) -> {
System.out.print(k + " ");
Arrays.stream(v)
// 加一个分隔符
.map(str -> str + " ")
.forEach(System.out::print);
System.out.println();
});

结果如下

1
2
3
4
5
1 1 aaa 2312 423 adas
2 2 dad rwqr fs FDSFDS
3 3 FDSFDSA EWR WE
4 4
5 5 dsafds fsd

扩展