jdk

JDK1.8新特性之Lambda表达式

闭包与高阶函数、惰性计算、没有“副作用”,一段匿名内部类,也可以是一段可以传递的代码

Posted by Sunfy on 2021-09-16
Words 1.5k and Reading Time 5 Minutes
Viewed Times
Viewed Times
Visitors In Total

函数式编程

函数编程非常关键的几个特性如下:

  • 闭包与高阶函数:函数编程支持函数作为第一类对象,有时称为 闭包或者 仿函数(functor)对象。实质上, 闭包是起函数的作用并可以像对象一样操作的对象。 与此类似,FP 语言支持 高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在 一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。
  • 惰性计算:在惰性计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值 时进行计算。延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于 程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。
  • 没有“副作用”: 所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局 变量的值),产生运算以外的其他结果。函数式编程强调没有”副作用”,意味着函数要保持 独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。 综上所述,函数式编程可以简言之是: 使用不可变值和函数, 函数对一个值进行处理, 映 射成另一个值。这个值在面向对象语言中可以理解为对象,另外这个值还可以作为函数的输 入。

Lambda表达式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码

语法

完整的Lambda表达式由三部分组成:参数列表、箭头、声明语句;

1
(Type1 param1, Type2 param2, ..., TypeN paramN) ‐> { statment1; statment2; //............. return statmentM;}
  1. 绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类 型,所以参数可以省略:
1
(param1,param2, ..., paramN) ‐> { statment1; statment2; //............. r eturn statmentM;}
  1. 当lambda表达式的参数个数只有一个,可以省略小括号:
1
param1 ‐> { statment1; statment2; //............. return statmentM;}
  1. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的 分号:
1
param1 ‐> statment

函数接口

函数接口是只有一个抽象方法的接口, 用作 Lambda 表达式的返回类型。 接口包路径为java.util.function,然后接口类上面都有@FunctionalInterface这个注解。

这些函数接口在使用Lambda表达式时做为返回类型,JDK定义了很多现在的函 数接口,实际自己也可以定义接口去做为表达式的返回,只是大多数情况下JDK 定义的直接拿来就可以用了。而且这些接口在JDK8集合类使用流操作时大量被 使用

类型检查、类型推断

Java编译器根据 Lambda 表达式上下文信息就能推断出参数的正确类型。 程序依 然要经过类型检查来保证运行的安全性, 但不用再显式声明类型罢了。 这就是 所谓的类型推断。Lambda 表达式中的类型推断, 实际上是 Java 7 就引入的目标 类型推断的扩展 有时候显式写出类型更易读,有时候去掉它们更易读。没有什么法则说哪种更 好;对于如何让代码更易读,你必须做出自己的选择

局部变量限制

Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像 匿名类一样。 它们被称作捕获Lambda。 Lambda可以没有限制地捕获(也就是在其主体 中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。

为什么局部变量有这些限制?

  • 实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变 量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用 的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变 量。因此, Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变 量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
  • 这一限制不鼓励你使用改变外部变量的典型命令式编程模式。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FunctionalInterface
public interface Lambda {
// todo @FunctionalInterface注解会检测接口是否有且只有一个抽象方法,还可以有默认方法和静态方法
public int test();
// 默认方法
default String getName() {
return "Name";
}
// 静态方法
static String getName2() {
return "Name2";
}

}
1
2
3
4
5
6
7
public class LambdaImpl implements Lambda{
@Override
public int test() {
System.out.println("实现类方法");
return 222;
}
}
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
public class MainTest {

private static void test(Lambda lambda) {
lambda.test();
}

public static void main(String[] args) {
// 传递实现类
test(new LambdaImpl());

// 匿名内部类
test(new Lambda() {
@Override
public int test() {
System.out.println("匿名内部类实现");
return 123;
}
});

// Lambda
test(() -> {
System.out.println("Lambda调用");
return 1;
});

// 当lambda表单式只包含一条语句时,可以省略大括号,return和语句结尾的分号;
test(() -> returnInt());
}

public static int returnInt() {
System.out.println("Lambda简化调用");
return 1234;
}

}

Copyright 2021 sunfy.top ALL Rights Reserved

...

...

00:00
00:00