函数编程-语法
表现形式
在 Java 语言中,lambda 对象有两种形式:lambda 表达式与方法引用
lambda 对象的类型是由它的行为决定的,如果有一些 lambda 对象,它们的入参类型、返回值类型都一致,那么它们可以看作是同一类的 lambda 对象,它们的类型,用函数式接口来表示
函数类型
练习:将 lambda 对象分类,见 PPT
函数接口的命名规律
- 带有 Unary 是一元的意思,表示一个参数
- 带有 Bi 或 Binary 是二元的意思,表示两个参数
- Ternary 三元
- Quatenary 四元
- …
方法引用也是类似,入参类型、返回值类型都一致的话,可以看作同一类的对象,也是用函数式接口表示
六种方法引用
类名::静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此静态方法
- 因此这个静态方法需要什么参数,函数对象也提供相应的参数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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 也能对应上,所以可以直接使用
输出
1 2
| Student[name=张无忌, sex=男] Student[name=宋青书, sex=男]
|
类名::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法
- 因此这个函数对象需要提供一个额外的对象参数,以便能够调用此非静态方法
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应
例1:
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 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
输出
例2:改写之前根据性别过滤的需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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
输出
1 2
| Student[name=张无忌, sex=男] Student[name=宋青书, sex=男]
|
例3:将学生对象仅保留学生的姓名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
输出
对象::非静态方法名
如何理解:
- 函数对象的逻辑部分是:调用此非静态方法
- 因为对象已提供,所以不必作为函数对象参数的一部分
- 非静态方法的剩余参数,与函数对象的剩余参数一一对应
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
| 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
对象中的非静态方法,最后的输出可以修改为
1
| .forEach(System.out::println);
|
这是因为
- forEach 这个高阶函数接收的函数类型(Consumer)是一个 T 类型参数,void 无返回值
- 而 System.out 对象中有非静态方法 void println(Object x) 与之一致,因此可以将此方法化为 lambda 对象给 forEach 使用
类名::new
对于构造方法,也有专门的语法把它们转换为 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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)); } }
|
this::非静态方法名
算是形式2的特例,只能用在类内部
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
| 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); } } }
|
super::非静态方法名
算是形式2的特例,只能用在类内部(用在要用 super 区分重载方法时)
1 2 3 4 5 6 7 8 9 10 11 12
| public class Type6Test { static class UtilExt extends Util { void hiOrder(Stream<Student> stream) { stream .filter(super::isFemale) .forEach(System.out::println); } } }
|
特例
函数接口和方法引用之间,可以差一个返回值,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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)
何为闭包,闭包就是函数对象与外界变量绑定在一起,形成的整体。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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→x+y 和 x=10,就形成了一个闭包
- 可以想象成,函数对象有个背包,背包里可以装变量随身携带,将来函数对象甭管传递到多远的地方,包里总装着个 x=10
- 有个限制,局部变量 x 必须是 final 或 effective final 的,effective final 意思就是,虽然没有用 final 修饰,但就像是用 final 修饰了一样,不能重新赋值,否则就语法错误。
- 意味着闭包变量,在装进包里的那一刻,就不能变化了
- 道理也简单,为了保证函数的不变性,防止破坏成道
- 闭包是一种给函数执行提供数据的手段,函数执行既可以使用函数入参,还可以使用闭包变量
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ClosureTest2 {
public static void main(String[] args) throws IOException { 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)
柯里化的作用是让函数对象分步执行(本质上是利用多个函数对象和闭包)
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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→... 是第一个函数对象,它的返回结果 b→... 是第二个函数对象
- 后者与前面的参数 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)判断语法正确性
1 2 3 4 5 6 7
| 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 表达式
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
| 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)使用函数接口解决问题
把下列方法中,可能存在变化的部分,抽象为函数对象,从外界传递进来
1 2 3 4 5 6 7 8 9 10
| 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; }
|
1 2 3 4 5 6 7 8
| static List<String> map(List<Integer> list) { List<String> result = new ArrayList<>(); for (Integer number : list) { result.add(String.valueOf(number)); } return result; }
|
1 2 3 4 5 6
| static void consume(List<Integer> list) { for (Integer number : list) { System.out.println(number); } }
|
1 2 3 4 5 6 7 8
| 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)写出等价的方法引用
1
| Function<String, Integer> lambda = (String s) -> Integer.parseInt(s);
|
1
| BiPredicate<List<String>, String> lambda = (list, element) -> list.contains(element);
|
1
| BiPredicate<Student, Object> lambda = (stu, obj) -> stu.equals(obj);
|
1
| Predicate<File> lambda = (file) -> file.exists();
|
1 2 3
| Runtime runtime = Runtime.getRuntime();
Supplier<Long> lambda = () -> runtime.freeMemory();
|
5)补充代码
1
| record Color(Integer red, Integer green, Integer blue) { }
|
如果想用 Color::new
来构造 Color 对象,还应当补充哪些代码
6)实现需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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】的条件
函数式编程-缘起
函数编程-Stream API