Java函数编程语法:函数类型、6种方法引用、闭包等知识点总结

函数编程语法

一、表现形式

在 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);
}
  1. Lambda1 lambda = a, b -> a - b ❌
  2. Lambda1 lambda = (c, d) -> c * d ✅
  3. Lambda1 lambda = (int a, b) -> a + b ❌
  4. 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);
    }
}
  1. Math::random

()->Math.random()

  1. Math::sqrt

(double number)->Math.sqrt(number)

  1. Student::getName

(Student stu)->stu.getName()

  1. Student::setName

(Student stu, String newName) -> stu.setName(newName)

  1. Student::hashCode

(Student stu) -> stu.hashCode()

  1. Student::equals

(Student stu, Object o) -> stu.equals(o)

假设已有对象 Student stu = new Student("张三");

  1. stu::getName

()->stu.getName()

  1. stu::setName

(String newName)->stu.setName(newName)

  1. 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】的条件

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值