jdk1.8对Lambda 表达式的支持,了Stream以实现对集合更方便地进行函数式编程。本文主要介绍jLambda表达式和Stream的一些常用使用方式,并通过一些代码小例子向大家展示怎么使用。
什么时函数式接口?
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
怎么写函数接口?
Java 1.8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
JDK 1.8之前已有的函数式接口:
JDK1.8后新加的函数式接口
接口 |
说明 |
BiConsumer |
代表了一个接受两个输入参数的操作,并且不返回任何结果 |
BiFunction |
代表了一个接受两个输入参数的方法,并且返回一个结果 |
BinaryOperator |
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
BiPredicate |
代表了一个两个参数的boolean值方法 |
BooleanSupplier | 代表了boolean值结果的提供方 |
Consumer |
代表了接受一个输入参数并且无返回的操作 |
Function |
接受一个输入参数,返回一个结果 |
Predicate |
接受一个输入参数,返回一个布尔值结果。 |
Supplier |
无参数,返回一个结果。 |
这些接口有很多,这里就不再一 一的说明,这些接口主要分为四大类supplier生产数据函数式接口、Consumer消费数据函数式接口、Predicate判断函数式接口、Function类型转换函数式接口。
其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。
Lambda 表达式语法
Lambda表达式又三部分组成,(参数列表-》->(表达式或者代码块)
如下例子:
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
Lambda表达式等价一个匿名内部类,只不过这里只能是一个实现了函数式接口的一个匿名内部类
public class RunnableTest {
@Test
public void RunnableLambdaTest(){
// 使用匿名内部类形式运行
Runnable r1 = new Runnable() {
public void run() {
System.out.println("this is r1");
}
};
// 执行内部方法, 并且没有传递任何参数
Runnable r2 = () -> System.out.println("this is r2");
// 运行
r1.run();
r2.run();
}
}
什么是流?
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。
中间操作:
终端操作:
下面我用一个个例子来对这些操作进行介绍、在介绍这些操作前我们先声明一个简单的User类,所有的Stream操作都是基于这个User集合进行介绍。
package com.sun.liems.model;
/***
* 人员类主要用于lambda表达式和Stream练习使用
* @author swh
*
*/
public class User {
// 账号ID
private String userId;
// 用户姓名
private String userName;
// 性别
private String sex;
// 年龄
private int age;
/***
* 构造器
* @param userId 用户id
* @param userName 姓名
* @param sex 性别
* @param age 年龄
*/
public User(String userId,String userName,String sex,int age) {
this.userId = userId;
this.userName =userName;
this.sex = sex;
this.age = age;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "账号:"+this.userId+" 姓名:"+this.userName+" 性别"+this.sex+" 年龄"+this.age;
}
}
1、map
可以将一种类型的值转换成另外一种类型,map 操作就可以使用该函数,将一个流中的值转换成一个新的流。其实就是就是实现一个Function接口。
Stream map(Function super T, ? extends R> mapper);
把一个人员转换成一个字符串
/***
* 把一个人员转换成一个字符串
* @return
*/
public static List map(){
return userList.stream()
.map(s->{
return s.toString();
})
.collect(Collectors.toList());
}
2、flatMap
flatmap 和map功能差不多,区别在于map的输出对应一个元素,必然是一个元素(null也是要返回),flatmap是0或者多个元素(为null的时候其实就是0个元素)。 flatmap的意义在于,一般的java方法都是返回一个结果,但是对于结果数量不确定的时候,用map这种java方法的方式,是不太灵活的,所以引入了flatmap。 flatMap可以把多个流程返回为一个流。
使用场景:如果map返回的一个数组或者是集合,可以用flatmap这些返回转换为同一个流。进行扁平化处理。
public static void flatMap(){
String[] words = new String[]{"Hello","World"};
List a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(s->Arrays.stream(s))
.collect(Collectors.toList());
a.forEach(System.out::print);
}
3、filter
filter操作用于数据过滤。
去除人员数组中的所有女性
public static List filter(){
return userList.stream()
.filter(s->s.getSex().equals("男"))
.collect(Collectors.toList());
}
4、distinct
distinct操作用于数据去重。distinct使用hashCode和equals方法来获取不同的元素。因此,我们的类必须实现hashCode和equals方法。
public static List distinct(){
return userList.stream()
.distinct()
.collect(Collectors.toList());
}
@Override
public boolean equals(Object o) {
User u = (User) o;
return this.getUserId().equals(u.getUserId());
}
@Override
public int hashCode() {
return this.getUserId().hashCode();
}
5、sorted
sorted操作用于以自然序排序
把人员按年龄排序
public static List sorted(){
return userList.stream()
.sorted(Comparator.comparing(s->s.getAge()))
.collect(Collectors.toList());
}
6、peek
peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等。map接收一个有返回值的λ表达式,之后Stream的泛型类型将转换为map参数λ表达式返回的类型。和forEach是有所区别的。peek是一个中间操作而forEach中间操作
public static List peek(){
return userList.stream()
.filter(s->s.getSex().equals("男"))
.peek(s->System.out.println(s))
.collect(Collectors.toList());
}
7、limit
limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
/***
* 取前4个用户
* @return
*/
public static List limit(){
return userList.stream()
.limit(4)
.collect(Collectors.toList());
}
8、skip
skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
/***
* 取出了前4个后面的用户
* @return
*/
public static List skip(){
return userList.stream()
.skip(4)
.collect(Collectors.toList());
}
9、forEach
作用是对容器中的每个元素执行action
指定的动作,也就是对元素进行遍历。
把所有人员打印出来
10、reduce
reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count、min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。
/***
* 求所有人员年龄的合
*/
public static void reduce() {
int ageSum = userList.stream()
.map(u->u.getAge())
.reduce(0,(sum,u)-> sum+u);
System.out.println(ageSum);
}
11、max及min
求最大值和最小值,此时max及min接受的是Comparator super T> comparator
/***
* 求年龄最大的人员
*/
public static void max() {
User user = userList.stream()
.max(Comparator.comparing(u->u.getAge()))
.get();
System.out.println(user);
}
12、collect
collect也就是收集器,是Stream一种通用的、从流生成复杂值的结构。只要将它传给collect方法,也就是所谓的转换方法,其就会生成想要的数据结构。这里不得不提下,Collectors这个工具库,在该库中封装了相应的转换方法。当然,Collectors工具库仅仅封装了常用的一些情景,如果有特殊需求,那就要自定义了。
方法 | 用途 |
toList | 把结果转换为一个List |
toSet | 把结果转换为一个Set |
groupingBy | 分类用,例如按某个属性分类转换为一个map |
toMap |
/**
* 把人员按性别分组
*
*/
public static void collect() {
Map
使用stream的toMap()函数时,当key重复,系统会报错相同的key不能形成一个map,那么需要解决这个问题,一:相同key的情况下,丢弃重复的只保留一个。
13、匹配方法
anyMatch:匹配上任何一个则返回 Boolean
allMatch:匹配所有的元素则返回 Boolean
noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true
Optional
/***
* 匹配
*/
public static void match() {
// 是否包含张三
boolean flagA = userList.stream()
.anyMatch(u->u.getUserName().equals("张三"));
System.out.println( flagA);
// 是否所以人都是18岁
boolean flagB = userList.stream()
.allMatch(u->u.getAge()==18);
System.out.println( flagB);
// 是否所有人都是成年人
boolean flagC = userList.stream()
.noneMatch(u->u.getAge()<18);
System.out.println(flagC);
}
steam提供的一些方法,简化了我们对集合的操作,让我们只关注代码逻辑,不用再去关注一些代码模板。能够让我们代码逻辑清晰。在代码运行效率其实是一样的。但是却可以提升我们的开发效率。
如果想下载上面例子的源码请到点击下面链接
https://github.com/sunwnehongl/LambadAndStream