Lamda表达式基本语法

函数式接口的应用

函数式接口就是Java类型系统中的接口,是只包含一个接口方法的特殊接口,我们在定义函数式接口时,可以使用注解 @FunctionalInterface 来完成语义化的检测

以下是函数式接口的定义代码示例:

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
// 定义函数式接口
// 这里使用此注解来帮助我们实现一个正确的函数式接口
@FunctionalInterface
public interface IUserCredential {

// 每一个函数式接口只能包含一个未实现的方法
String verifyUser(String username);

// 这里注意,如果包含了两个就会报错,在编译期
// boolean test();

// 有一个特例,因为Java中的类都继承了Object,所以Object类中的方法可以写在这里不实现它
String toString();

// 注意:接口中可以包含实现的静态方法
static boolean verifyMessage(String msg) {
if (msg != null) {
return true;
}
return false;
}

// 1.8中可以在接口中定义默认的方法实现
default String getCredential(String username) {
// 模拟方法
if ("admin".equals(username)) {
return "admin + 系统管理员用户";
} else {
return "commons + 普通会员用户";
}
}
}

以下是函数式接口的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 首先是普通接口的调用实现

// 1. 普通实现,直接初始化接口实现类
IUserCredential ic = new UserCredentialImpl();
System.out.println(ic.verifyUser("admin"));

// 2. 静态方法,直接调用
String msg = "hello world";
IMessageFormat.verifyMessage(msg)

// 3. 匿名内部类,实现接口的抽象方法
IUserCredential ic2 = new IUserCredential() {
@Override
public String verifyUser(String username) {
return "admin".equals(username)?"管理员":"会员";
}
};

// 4. 使用Lamda的方式实现接口的抽象方法
// 这里我们需要注意,Lamda是通过返回的接收对象确定它实现的是哪个接口的方法,所以当有多个函数式接口中都有相同的未实现方法参数列表时,我们是通过返回的接收对象来绑定实现的接口类型,,如此示例中,就是通过 IUserCredential ic3 这个返回的接收接口来类型推导,绑定实现的接口,所以不会有冲突的情况
IUserCredential ic3 = (String username) -> {
return "admin".equals(username)?"lbd管理员": "lbd会员";
};

以下是JDK8提供的常见的函数式接口

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
// 1. Predicate
Predicate<String> pre = (String username) -> {
return "admin".equals(username);
};
System.out.println(pre.test("manager"));

// 2. Consumer
Consumer<String> con = (String message) -> {
System.out.println("要发送的消息:" + message);
System.out.println("消息发送完成");
};
con.accept("hello 慕课网的学员们..");

// 3. Function
Function<String, Integer> fun = (String gender) -> {
return "male".equals(gender)?1:0;
};
System.out.println(fun.apply("male"));

// 4. Supplier
Supplier<String> sup = () -> {
return UUID.randomUUID().toString();
};
System.out.println(sup.get());

// 5. UnaryOperator
UnaryOperator<String> uo = (String img)-> {
img += "[100x200]";
return img;
};
System.out.println(uo.apply("原图--"));

// 6. BinaryOperator
BinaryOperator<Integer> bo = (Integer i1, Integer i2) -> {
return i1 > i2? i1: i2;
};
System.out.println(bo.apply(12, 13));

java.util.function提供了大量的函数式接口,以上示例使用总结如下:

  1. Predicate 接收参数T对象,返回一个boolean类型结果

  2. Consumer 接收参数T对象,没有返回值

  3. Function 接收参数T对象,返回R对象

  4. Supplier 不接受任何参数,直接通过get()获取指定类型的对象

  5. UnaryOperator 接口参数T对象,执行业务处理后,返回更新后的T对象

  6. BinaryOperator 接口接收两个T对象,执行业务处理后,返回一个T对象

lambda表达式的基本语法:

  1. 声明:就是和lambda表达式绑定的接口类型

  2. 参数:包含在一对圆括号中,和绑定的接口中的抽象方法中的参数个数及顺序一致

  3. 操作符:->

  4. 执行代码块:包含在一对大括号中,出现在操作符号的右侧

[接口声明] = (参数) -> {执行代码块};

Lamda表达式的变量捕获

首先我们查看原始代码风格中的变量捕获方式及存在的问题:

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
public class App2 {
String s1 = "全局变量";
String s3 = "全局变量s3";

// 1. 匿名内部类型中对于变量的访问
public void testInnerClass() {
String s2 = "局部变量";

new Thread(new Runnable() {
String s3 = "内部变量s3";
@Override
public void run() {
// 访问全局变量,这里用this访问到的是外部类中的全局变量s1,因为在内部类中并没有定义s1
System.out.println(this.s1);
System.out.println(s1);

System.out.println(s2);
// 局部变量的访问,~不能对局部变量进行数据的修改[final]
// s2 = "hello";

// 这里访问的就是内部类中定义的内部变量,因为就近原则,所以这里很多时候就会被初学者产生歧义
System.out.println(s3);
System.out.println(this.s3);

}
}).start();
}

我们再用lambda表达式变量捕获的方式来实现一遍:

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
public class App2 {
String s1 = "全局变量";
String s3 = "全局变量s3";

public void testLambda() {
String s2 = "局部变量lambda";

new Thread(() -> {
String s3 = "内部变量lambda";

// 访问全局变量
System.out.println(this.s1);// this关键字,表示的就是所属方法所在类型的对象
// 访问局部变量
System.out.println(s2);
// 这里不能进行数据修改,默认推导变量的修饰符:final,因为获取到的s2是在栈中的
s2 = "hello";

// 这里我们捕获到的变量就是外部定义的s3全局变量,这里就不会产生歧义
System.out.println(s3);
// 这里获取到的s3变量是可以修改的,因为此外部的变量是存放在堆中的
s3 = "labmda 内部变量直接修改";
System.out.println(s3);
}).start();
}
}

通过以上代码示例,我们就可以看出Lamda表达式对于变量捕获的影响

方法重载对于lmabda表达式的影响

具体我们通过代码来查看:

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
public class App4 {

interface Param1 {
void outInfo(String info);
}

interface Param2 {
void outInfo(String info);
}

// 定义重载的方法
public void lambdaMethod(Param1 param) {
param.outInfo("hello param1 imooc!");
}
public void lambdaMethod(Param2 param) {
param.outInfo("hello param2 imooc");
}

// 这里我们通过匿名内部类来实现此重载方法,这样是没有任何问题的
public static void main(String[] args) {
App4 app = new App4();

app.lambdaMethod(new Param1() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});

app.lambdaMethod(new Param2() {
@Override
public void outInfo(String info) {
System.out.println("------");
System.out.println(info);
}
});

// 这里我们通过lamda表达式来重载,发现是失败的, 会报错,因为它无法推导出具体要绑定的接口是哪个
// app.lambdaMethod( (String info) -> {
// System.out.println(info);
// });
}
}

以上示例说明,Lamda是存在类型推导的,lambda表达式存在类型检查-> 自动推导lambda表达式的目标类型,具体推导流程如下

1
2
3
4
5
6
7
lambdaMethod() -> 方法 -> 重载方法
-> Param1 函数式接口
-> Param2 函数式接口
调用方法-> 传递Lambda表达式-> 自动推导->
-> Param1 | Param2

这里到了推导的最后一步,发现有两个接口满足此推导逻辑,导致产生了歧义,这样就出现了问题,导致无法编译

Lamda的方法引用的实现

我们还是通过示例的代码来分析:

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
// 首次定义用于示例的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private String name; // 姓名
private int age; // 年龄

// 这里使用lombox,有默认的构造方式 AllArgsConstructor

// 静态方法
public static int compareByAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}

// 实例方法
public int compareByName(Person p1, Person p2) {
return p1.getName().hashCode() - p2.getName().hashCode();
}
}

// 静态方法调用
Person::compareByAge

// 实例方法调用
Person pu = new Person();
pu::compareByName

// 构造方法引用:绑定函数式接口
Person ip = Person::new; // 无参构造
Person person = ip.initPerson("jerry", "男", 22); // 有参构造

Stream常见操作API介绍

Stream的获取

  1. 批量数据 -> Stream对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 多个数据
    Stream stream = Stream.of("admin", "tom", "damu");

    // 数组
    String [] strArrays = new String[] {"xueqi", "biyao"};
    Stream stream2 = Arrays.stream(strArrays);

    // 列表
    List<String> list = new ArrayList<>();
    list.add("少林");
    list.add("武当");
    Stream stream3 = list.stream();

    // 集合
    Set<String> set = new HashSet<>();
    set.add("武当长拳");
    set.add("青城剑法");
    Stream stream4 = set.stream();

    // Map
    Map<String, Integer> map = new HashMap<>();
    map.put("tom", 1000);
    map.put("jerry", 1200);
    Stream stream5 = map.entrySet().stream();
  2. Stream对象对于基本数据类型的功能封装

注意,当前只支持 int / long / double 这三种数据类型

1
2
3
IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println);
IntStream.range(1, 5).forEach(System.out::println); // 开区间
IntStream.rangeClosed(1, 5).forEach(System.out::println); // 闭区间
  1. Stream对象 –> 转换得到指定的数据类型

注意:这里涉及到的都是终结操作,既每一个操作后stream流就被关闭了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数组
Object [] objx = stream.toArray(String[]::new);

// 字符串
String str = stream.collect(Collectors.joining()).toString();
System.out.println(str);

// 列表
List<String> listx = (List<String>) stream.collect(Collectors.toList());
System.out.println(listx);

// 集合
Set<String> setx = (Set<String>) stream.collect(Collectors.toSet());
System.out.println(setx);

// Map
Map<String, String> mapx = (Map<String, String>) stream.collect(Collectors.toMap(x->x, y->"value:"+y));
System.out.println(mapx);

Stream常见API介绍

  1. 聚合操作

  2. stream的处理流程

    数据源
    数据转换
    获取结果

  3. 获取Stream对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*      1. 从集合或者数组中获取[**]
* Collection.stream(),如accounts.stream()
* Collection.parallelStream()
* Arrays.stream(T t)
* 2. BufferReader
* BufferReader.lines()-> stream()
* 3. 静态工厂
* java.util.stream.IntStream.range()..
* java.nio.file.Files.walk()..
* 4. 自定构建
* java.util.Spliterator
* 5. 更多的方式..
* Random.ints()
* Pattern.splitAsStream()..
  1. 中间操作API{intermediate}
  • 操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作,需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。

  • 中间操作:就是业务逻辑处理。

  • 中间操作过程:无状态:数据处理时,不受前置中间操作的影响,如下面所列操作:
    map/filter/peek/parallel/sequential/unordered

  • 有状态:数据处理时,受到前置中间操作的影响,如下面所列操作:
    distinct/sorted/limit/skip

  1. 终结操作|结束操作{Terminal}
  • 需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。

  • 终结操作:非短路操作:当前的Stream对象必须处理完集合中所有 数据,才能得到处理结果,如下面所列操作:
    forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator

  • 短路操作:当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果。
    anyMatch/allMatch/noneMatch/findFirst/findAny等

  • Short-circuiting,无限大的Stream-> 有限大的Stream。

Stream常见的API操作

  1. 基本常用的操作支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义一个集合数据
List<String> accountList = new ArrayList<>();
accountList.add("songjiang");
accountList.add("lujunyi");
accountList.add("wuyong");


// 1.map() 中间操作,map()方法接收一个Functional接口
accountList = accountList.stream().map(x->"梁山好汉:" + x).collect(Collectors.toList());

// 2.filter() 添加过滤条件,过滤符合条件的用户
accountList = accountList.stream().filter(x-> x.length() > 5).collect(Collectors.toList());

// 3.forEach 增强型循环
accountList.forEach(x-> System.out.println("forEach->" + x));

// 4.peek() 中间操作,迭代数据完成数据的依次处理过程,这里只循环一次,但是能处理两件事情
accountList.stream()
.peek(x -> System.out.println("peek 1: " + x))
.peek(x -> System.out.println("peek 2:" + x))
.forEach(System.out::println);
  1. Stream中对于数字运算的支持
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
// 定义一个数字集合
List<Integer> intList = new ArrayList<>();
intList.add(20);
intList.add(19);
intList.add(7);
intList.add(12);
intList.add(5);

// 1.skip() 中间操作,有状态,跳过部分数据,示例跳过前三个数据
intList.stream().skip(3).forEach(System.out::println);

// 2.limit() 中间操作,有状态,限制输出数据量,示例跳过前三个数据,然后取跳过后的前两个数据
intList.stream().skip(3).limit(2).forEach(System.out::println);

// 3.distinct() 中间操作,有状态,剔除重复的数据
intList.stream().distinct().forEach(System.out::println);

// 4.sorted() 中间操作,有状态,排序

// 5.max() 获取最大值
Optional optional = intList.stream().max((x, y)-> x-y);
System.out.println(optional.get());

// 6.min() 获取最小值
Optional optional = intList.stream().min((x, y)-> x-y);
System.out.println(optional.get());

// 7.reduce() 合并处理数据
Optional optional2 = intList.stream().reduce((sum, x)-> sum + x);
System.out.println(optional2.get());

补充 ParallelStream

ParallelStream是一个并发多线程的Stream

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Optional optional = list.parallelStream().max(Integer::compare);
System.out.println(optional.get());

// 整数列表,注意:下面示例是为了说明并行的Stream会出现线程安全问题
List<Integer> lists = new ArrayList<Integer>();
// 增加数据
for (int i = 0; i < 1000; i++){
lists.add(i);
}

// 串行Stream
List<Integer> list2 = new ArrayList<>();
lists.stream().forEach(x->list2.add(x));
System.out.println(lists.size());
System.out.println(list2.size());
// 并行Stream
List<Integer> list3 = new ArrayList<>();
lists.parallelStream().forEach(x-> list3.add(x));
System.out.println(list3.size());
//
List<Integer> list4 = lists.parallelStream().collect(Collectors.toList());
System.out.println(list4.size());

最后更新: 2020年02月18日 11:22

原始链接: https://jjw-story.github.io/2019/12/29/Lamda/

× 请我吃糖~
打赏二维码