Java函数式编程一
函数式开发旨在帮助程序员写出更好的代码,如Java8引入的流(Stream)使程序员得以站在更高的抽象层次对集合进行操作!Stream 是用函数式编程方式在集合类上进行复杂操作的工具。
传统循环操作有很多弊端如需要写很多样板代码、行为意图不清晰等,嵌套循环更严重,另外传统操作对于并行开发也很麻烦!使用Stream对集合进行操作,可以避免这些。如:
计算集合中大于8的元素个数
List list = Arrays.asList(2, 5, 3, 9, 25, 88);
//传统操作
int count = 0;
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Integer integer = iterator.next();
if (integer > 8){
count++;
}
}
//使用Stream 可被分解为两步更简单的操作 行为更加明确
//1、过滤 2、计算
long count = list.stream()
.filter(item -> item > 8)
.count();
上面Stream的例子有两个行为,但是其内部只进行了一次循环,这里涉及两个概念惰性求值方法和及早求值方法。
第一个行为
list.stream()
.filter(item -> item > 8)
这里其实未做什么实际性的工作,只是描述了Stream,最终不产生新的集合,像filter这样的方法就叫做惰性求值方法;而像count()
这样最终会从Stream产生值的方法叫及早求值方法。
这个可以从侧面证明一下,如在过滤器中加入println语句,来输出元素:
list.stream()
.filter(item -> {
System.out.println(item);
return item > 8;
});
可以发现是没有任何元素输出的,只有加上及早求值方法,才会有输出!
判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream, 那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果!
collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。
//Stream 的 of 方法使用一组初始值生成新的 Stream
List collect = Stream.of(9,6,8,25,88)
.filter(item -> item > 8)
.collect(Collectors.toList());
System.out.println(collect);
[9, 25, 88]
如果有一个函数可以将一种类型的值转换成另外一种类型,那么map 操作就可以使用该函数,将一个流中的值转换成一个新的流。
//接受一个Function类型的lambda表达式 该类型函数接口上一篇博文有介绍
Stream map(Function super T, ? extends R> mapper);
//将集合中每个元素的第一个字母a替换为A
List collect2 = Stream.of("a", "world", "Java")
.map(s -> s.replaceFirst("a", "A"))
.collect(Collectors.toList());
System.out.println(collect2);
[A, world, JAva, AAr]
遍历数据并检查其中的元素时,可尝试使用 Stream 中提供的新方法 filter .
//接受一个Predicate类型的接口
Stream filter(Predicate super T> predicate);
//找出一组字符串中以数字开头的
List collect3 = Stream.of("1aa", "abc", "a3b")
.filter(s -> Character.isDigit(s.charAt(0)))
.collect(Collectors.toList());
System.out.println(collect3);
[1aa]
flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream .
List list1 = Arrays.asList(1, 3, 5);
List list2 = Arrays.asList(2, 4, 6);
List list3 = Arrays.asList(3, 6, 9);
List collect4 = Stream.of(list1, list2, list3)
.flatMap(number -> number.stream())
.collect(Collectors.toList());
System.out.println(collect4);
[1, 3, 5, 2, 4, 6, 3, 6, 9]
求最大值和最小值
Optional min(Comparator super T> comparator);
Optional max(Comparator super T> comparator);
//求最小值
Integer integer = Stream.of(2, 5, 8, 3)
.min(Comparator.comparing(item -> item))
.get();
System.out.println(integer);
//找出最长的字符串
String s = Stream.of("as", "java", "hello")
.max(Comparator.comparing(item -> item.length()))
.get();
System.out.println(s);
2
hello
reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count、min 和 max 方 法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。
Integer result = Stream.of(2, 8, 5)
.reduce(0, (acc, element) -> acc + element);
System.out.println(result);
reduce不太好理解,上面代码就相当于下面的实现,reduce的第一个参数0相当于下面的initialValue的值,第二个参数是一个lambda表达式,里面的acc是一个累加器相当于下面的result!
int result = initialValue;
for (Integer element : collection) {
result= result+ element;
}
运行解果
15
reduce方法有两种形式,一种就是上面提到的有一个初始值,另一种不需要初始值。没有初始值的reduce第一步使用的前面两个元素,区别与有初始值操作的每一步只使用一个元素!
Stream接口的方法有很多,有时候会让人难以选择,这时候就要先明确问题,然后将问题拆分为简单的 Stream 操作,最后找出每一步对应的 Stream API 。具体看下面的几个例子:
找出集合元素中偶数的最大值
/**
* 1、找出偶数 -> filter
* 2、找最大值 -> max
*/
Integer integer = Stream.of(3, 6, 9, 8, 4)
.filter(i -> i % 2 == 0)
.max(Comparator.comparing(item -> item))
.get();
System.out.println(integer);
8
找出所有学号以2018
开头的学生的手机号
/**
* 1、找出目标学生 -> filter
* 2、使用 map 方法将学生转换为手机号-> map
*/
studentList.stream()
.filter(student -> student.getId().startsWith("2018"))
.map(student-> student.getPhone())
.collect(Collectors.toList());
找出各个班级学生年龄大于18的学生姓名
/**
* 1、将各个班级的学生连成一个Stream -> flatMap
* 2、找出大于18的学生
* 3、将学生转换为name
*/
List collect = classList.stream()
.flatMap(c -> c.getStudents().stream())
.filter(student -> student.getAge() > 18)
.map(student -> student.getName())
.collect(Collectors.toList());