函数编程语法
一、表现形式
在 Java 语言中,lambda 对象有两种形式:lambda 表达式与方法引用
lambda 对象的类型是由它的行为决定的,如果有一些 lambda 对象,它们的入参类型、返回值类型都一致,那么它们可以看作是同一类的 lambda 对象,它们的类型,用函数式接口来表示
二、函数类型
练习:将 lambda 对象分类,见 PPT
函数接口的命名规律
- 带有 Unary 是一元的意思,表示一个参数
- 带有 Bi 或 Binary 是二元的意思,表示两个参数
- Ternary 三元
- Quatenary 四元
- ...
方法引用也是类似,入参类型、返回值类型都一致的话,可以看作同一类的对象,也是用函数式接口表示
三、六种方法引用
1)类名::静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此静态方法
- 因此这个静态方法需要什么参数,函数对象也提供相应的参数即可
public class Type2Test {
public static void main(String[] args) {
/*
需求:挑选出所有男性学生
*/
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(Type2Test::isMale)
.forEach(student -> System.out.println(student));
}
static boolean isMale(Student student) {
return student.sex.equals("男");
}
record Student(String name, String sex) {
}
}
- filter 这个高阶函数接收的函数类型(Predicate)是:一个 T 类型的入参,一个 boolean 的返回值
- 因此我们只需要给它提供一个相符合的 lambda 对象即可
- isMale 这个静态方法有入参 Student 对应 T,有返回值 boolean 也能对应上,所以可以直接使用
输出
Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]
2)类名::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法
- 因此这个函数对象需要提供一个额外的对象参数,以便能够调用此非静态方法
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应
例1:
public class Type3Test {
public static void main(String[] args) {
highOrder(Student::hello);
}
static void highOrder(Type3 lambda) {
System.out.println(lambda.transfer(new Student("张三"), "你好"));
}
interface Type3 {
String transfer(Student stu, String message);
}
static class Student {
String name;
public Student(String name) {
this.name = name;
}
public String hello(String message) {
return this.name + " say: " + message;
}
}
}
上例中函数类型的
- 参数1 对应着 hello 方法所属类型 Student
- 参数2 对应着 hello 方法自己的参数 String
- 返回值对应着 hello 方法自己的返回值 String
输出
张三 say: 你好
例2:改写之前根据性别过滤的需求
public class Type2Test {
public static void main(String[] args) {
/*
需求:挑选出所有男性学生
*/
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(Student::isMale)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
}
- filter 这个高阶函数接收的函数类型(Predicate)是:一个 T 类型的入参,一个 boolean 的返回值
- 因此我们只需要给它提供一个相符合的 lambda 对象即可
- 它的入参1 T 对应着 isMale 非静态方法的所属类型 Student
- 它没有其它参数,isMale 方法也没有参数
- 返回值都是 boolean
输出
Student[name=张无忌, sex=男]
Student[name=宋青书, sex=男]
例3:将学生对象仅保留学生的姓名
public class Type2Test {
public static void main(String[] args) {
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.map(Student::name)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
}
- map 这个高阶函数接收的函数类型是(Function)是:一个 T 类型的参数,一个 R 类型的返回值
- 它的入参1 T 对应着 name 非静态方法的所属类型 Student
- 它没有剩余参数,name 方法也没有参数
- 它的返回值 R 对应着 name 方法的返回值 String
输出
张无忌
周芷若
宋青书
3)对象::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法
- 因为对象已提供,所以不必作为函数对象参数的一部分
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应
public class Type4Test {
public static void main(String[] args) {
Util util = new Util(); // 对象
Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
)
.filter(util::isMale)
.map(util::getName)
.forEach(student -> System.out.println(student));
}
record Student(String name, String sex) {
boolean isMale() {
return this.sex.equals("男");
}
}
static class Util {
boolean isMale(Student student) {
return student.sex.equals("男");
}
String getName(Student student) {
return student.name();
}
}
}
其实较为典型的一个应用就是 System.out
对象中的非静态方法,最后的输出可以修改为
.forEach(System.out::println);
这是因为
- forEach 这个高阶函数接收的函数类型(Consumer)是一个 T 类型参数,void 无返回值
- 而 System.out 对象中有非静态方法 void println(Object x) 与之一致,因此可以将此方法化为 lambda 对象给 forEach 使用
4)类名::new
对于构造方法,也有专门的语法把它们转换为 lambda 对象
函数类型应满足
- 参数部分与构造方法参数一致
- 返回值类型与构造方法所在类一致
例如:
public class Type5Test {
static class Student {
private final String name;
private final int age;
public Student() {
this.name = "某人";
this.age = 18;
}
public Student(String name) {
this.name = name;
this.age = 18;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
interface Type51 {
Student create();
}
interface Type52 {
Student create(String name);
}
interface Type53 {
Student create(String name, int age);
}
public static void main(String[] args) {
hiOrder((Type51) Student::new);
hiOrder((Type52) Student::new);
hiOrder((Type53) Student::new);
}
static void hiOrder(Type51 creator) {
System.out.println(creator.create());
}
static void hiOrder(Type52 creator) {
System.out.println(creator.create("张三"));
}
static void hiOrder(Type53 creator) {
System.out.println(creator.create("李四", 20));
}
}
5)this::非静态方法名
算是形式2的特例,只能用在类内部
public class Type6Test {
public static void main(String[] args) {
Util util = new UtilExt();
util.hiOrder(Stream.of(
new Student("张无忌", "男"),
new Student("周芷若", "女"),
new Student("宋青书", "男")
));
}
record Student(String name, String sex) {
}
static class Util {
boolean isMale(Student student) {
return student.sex.equals("男");
}
boolean isFemale(Student student) {
return student.sex.equals("女");
}
void hiOrder(Stream<Student> stream) {
stream
.filter(this::isMale)
.forEach(System.out::println);
}
}
}
6)super::非静态方法名
算是形式2的特例,只能用在类内部(用在要用 super 区分重载方法时)
public class Type6Test {
//...
static class UtilExt extends Util {
void hiOrder(Stream<Student> stream) {
stream
.filter(super::isFemale)
.forEach(System.out::println);
}
}
}
7)特例
函数接口和方法引用之间,可以差一个返回值,例如
public class ExceptionTest {
public static void main(String[] args) {
Runnable task1 = ExceptionTest::print1;
Runnable task2 = ExceptionTest::print2;
}
static void print1() {
System.out.println("task1 running...");
}
static int print2() {
System.out.println("task2 running...");
return 1;
}
}
- 可以看到 Runnable 接口不需要返回值,而实际的函数对象多出的返回值也不影响使用
四、闭包(Closure)
何为闭包,闭包就是函数对象与外界变量绑定在一起,形成的整体。例如
public class ClosureTest1 {
interface Lambda {
int add(int y);
}
public static void main(String[] args) {
int x = 10;
highOrder(y -> x + y);
}
static void highOrder(Lambda lambda) {
System.out.println(lambda.add(20));
}
}
- 代码中的 $y \rightarrow x + y$ 和 $x = 10$,就形成了一个闭包
- 可以想象成,函数对象有个背包,背包里可以装变量随身携带,将来函数对象甭管传递到多远的地方,包里总装着个 $x = 10$
- 有个限制,局部变量 x 必须是 final 或 effective final 的,effective final 意思就是,虽然没有用 final 修饰,但就像是用 final 修饰了一样,不能重新赋值,否则就语法错误。
- 意味着闭包变量,在装进包里的那一刻,就不能变化了
- 道理也简单,为了保证函数的不变性,防止破坏成道
- 闭包是一种给函数执行提供数据的手段,函数执行既可以使用函数入参,还可以使用闭包变量
例
public class ClosureTest2 {
// 闭包作用:给函数对象提供参数以外的数据
public static void main(String[] args) throws IOException {
// 创建 10 个任务对象,并且每个任务对象给一个任务编号
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int k = i + 1;
Runnable task
= () -> System.out.println(Thread.currentThread()+":执行任务" + k);
list.add(task);
}
ExecutorService service = Executors.newVirtualThreadPerTaskExecutor();
for (Runnable task : list) {
service.submit(task);
}
System.in.read();
}
}
五、柯里化(Carrying)
柯里化的作用是让函数对象分步执行(本质上是利用多个函数对象和闭包)
例如:
public class Carrying1Test {
public static void main(String[] args) {
highOrder(a -> b -> a + b);
}
static void highOrder(Step1 step1) {
Step2 step2 = step1.exec(10);
System.out.println(step2.exec(20));
System.out.println(step2.exec(50));
}
interface Step1 {
Step2 exec(int a);
}
interface Step2 {
int exec(int b);
}
}
代码中
- $a \rightarrow ...$ 是第一个函数对象,它的返回结果 $b \rightarrow ...$ 是第二个函数对象
- 后者与前面的参数 a 构成了闭包
- step1.exec(10) 确定了 a 的值是 10,返回第二个函数对象 step2,a 被放入了 step2 对象的背包记下来了
- step2.exec(20) 确定了 b 的值是 20,此时可以执行 a + b 的操作,得到结果 30
- step2.exec(50) 分析过程类似
六、高阶函数(Higher-Order Functions)
1) 内循环
2) 遍历二叉树
3) 简单流
4) 简单流-化简
5) 简单流-收集
七、综合练习
✅❌
1)判断语法正确性
interface Lambda1 {
int op(int a, int b);
}
interface Lambda2 {
void op(Object obj);
}
Lambda1 lambda = a, b -> a - b
❌Lambda1 lambda = (c, d) -> c * d
✅Lambda1 lambda = (int a, b) -> a + b
❌Lambda2 lambda = Object a -> System.out.println(a)
❌
2)写出等价的 lambda 表达式
static class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
Math::random
()->Math.random()
Math::sqrt
(double number)->Math.sqrt(number)
Student::getName
(Student stu)->stu.getName()
Student::setName
(Student stu, String newName) -> stu.setName(newName)
Student::hashCode
(Student stu) -> stu.hashCode()
Student::equals
(Student stu, Object o) -> stu.equals(o)
假设已有对象 Student stu = new Student("张三");
stu::getName
()->stu.getName()
stu::setName
(String newName)->stu.setName(newName)
Student::new
(String name)->new Student(name)
3)使用函数接口解决问题
把下列方法中,可能存在变化的部分,抽象为函数对象,从外界传递进来
static List<Integer> filter(List<Integer> list) {
List<Integer> result = new ArrayList<>();
for (Integer number : list) {
// 筛选:判断是否是偶数,但以后可能改变判断规则
if((number & 1) == 0) {
result.add(number);
}
}
return result;
}
static List<String> map(List<Integer> list) {
List<String> result = new ArrayList<>();
for (Integer number : list) {
// 转换:将数字转为字符串,但以后可能改变转换规则
result.add(String.valueOf(number));
}
return result;
}
static void consume(List<Integer> list) {
for (Integer number : list) {
// 消费:打印,但以后可能改变消费规则
System.out.println(number);
}
}
static List<Integer> supply(int count) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
// 生成:随机数,但以后可能改变生成规则
result.add(ThreadLocalRandom.current().nextInt());
}
return result;
}
4)写出等价的方法引用
Function<String, Integer> lambda = (String s) -> Integer.parseInt(s);
BiPredicate<List<String>, String> lambda = (list, element) -> list.contains(element);
BiPredicate<Student, Object> lambda = (stu, obj) -> stu.equals(obj);
Predicate<File> lambda = (file) -> file.exists();
Runtime runtime = Runtime.getRuntime();
Supplier<Long> lambda = () -> runtime.freeMemory();
5)补充代码
record Color(Integer red, Integer green, Integer blue) { }
如果想用 Color::new
来构造 Color 对象,还应当补充哪些代码
6)实现需求
record Student(String name, int age) { }
static void highOrder(Predicate<Student> predicate) {
List<Student> list = List.of(
new Student("张三", 18),
new Student("张三", 17),
new Student("张三", 20)
);
for (Student stu : list) {
if (predicate.test(stu)) {
System.out.println("通过测试");
}
}
}
传入参数时,分别用
- 类名::静态方法名
- 类名::非静态方法名
来表示【学生年龄大于等于18】的条件