一. 函数伊始

黑马Java函数式编程全面精讲

在线学习:https://www.bilibili.com/video/BV1fz421C7tj/

网盘链接:https://pan.baidu.com/s/143pBbK_YFVdjdpZLHHW07g?pwd=rvfi

提取码:rvfi

1. 什么是函数

什么是函数呢?函数即规则

数学上:

在这里插入图片描述

例如:

INPUTf(x)OUTPUT
1?1
2?4
3?9
4?16
5?25
  • f ( x ) = x 2 f(x) = x^2 f(x)=x2 是一种规律, input 按照此规律变化为 output
  • 很多规律已经由人揭示,例如 e = m ⋅ c 2 e = m \cdot c^2 e=mc2
  • 程序设计中可以自己去制定规律,一旦成为规则的制定者,你就是神

2. 大道无情

2.1 无情

何为无情:

  • 只要输入相同,无论多少次调用,无论什么时间调用,输出相同。

2.2 佛祖成道

例如

public class TestMutable {

    public static void main(String[] args) {
        System.out.println(pray("张三"));
        System.out.println(pray("张三"));
        System.out.println(pray("张三"));
    }

    static class Buddha {
        String name;

        public Buddha(String name) {
            this.name = name;
        }
    }

    static Buddha buddha = new Buddha("佛祖");

    static String pray(String person) {
        return (person + "向[" + buddha.name + "]虔诚祈祷");
    }
}

以上 pray 的执行结果,除了参数变化外,希望函数的执行规则永远不变

张三向[佛祖]虔诚祈祷
张三向[佛祖]虔诚祈祷
张三向[佛祖]虔诚祈祷

然而,由于设计上的缺陷,函数引用了外界可变的数据,如果这么使用

buddha.name = "魔王";
System.out.println(pray("张三"));

结果就会是

张三向[魔王]虔诚祈祷

问题出在哪儿呢?函数的目的是除了参数能变化,其它部分都要不变,这样才能成为规则的一部分。佛祖要成为规则的一部分,也要保持不变

改正方法

static class Buddha {
    final String name;

    public Buddha(String name) {
        this.name = name;
    }
}

record Buddha(String name) { }
  • 不是说函数不能引用外界的数据,而是它引用的数据必须也能作为规则的一部分
  • 让佛祖不变,佛祖才能成为规则

2.3 函数与方法

方法本质上也是函数。不过方法绑定在对象之上,它是对象个人法则

函数是

  • 函数(对象数据,其它参数)

而方法是

  • 对象数据.方法(其它参数)

2.4 不变的好处

只有不变,才能在滚滚时间洪流中屹立不倒,成为规则的一部分。

多线程编程中,不变意味着线程安全

2.5 合格的函数无状态

3. 大道无形

3.1 函数化对象

函数本无形,也就是它代表的规则:位置固定、不能传播。

若要有形,让函数的规则能够传播,需要将函数化为对象。

public class MyClass {
    static int add(int a, int b) {
        return a + b;
    }
} 

interface Lambda {
    int calculate(int a, int b);
}

Lambda add = (a, b) -> a + b; // 它已经变成了一个 lambda 对象

区别在哪?

  • 前者是纯粹的一条两数加法规则,它的位置是固定的,要使用它,需要通过 MyClass.add 找到它,然后执行
  • 而后者(add 对象)就像长了腿,它的位置是可以变化的,想去哪里就去哪里,哪里要用到这条加法规则,把它传递过去
  • 接口的目的是为了将来用它来执行函数对象,此接口中只能有一个方法定义

函数化为对象做个比喻

  • 之前是大家要统一去西天取经
  • 现在是每个菩萨、罗汉拿着经书,入世传经

例如

public class Test {
    interface Lambda {
        int calculate(int a, int b);
    }

    static class Server {
        public static void main(String[] args) throws IOException {
            ServerSocket ss = new ServerSocket(8080);
            System.out.println("server start...");
            while (true) {
                Socket s = ss.accept();
                Thread.ofVirtual().start(() -> {
                    try {
                        ObjectInputStream is = new ObjectInputStream(s.getInputStream());
                        Lambda lambda = (Lambda) is.readObject();
                        int a = ThreadLocalRandom.current().nextInt(10);
                        int b = ThreadLocalRandom.current().nextInt(10);
                        System.out.printf("%s %d op %d = %d%n",
                   s.getRemoteSocketAddress().toString(), a, b, lambda.calculate(a, b));
                    } catch (IOException | ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                });
            }
        }
    }

    static class Client1 {
        public static void main(String[] args) throws IOException {
            try(Socket s = new Socket("127.0.0.1", 8080)){
                Lambda lambda = (Lambda & Serializable) (a, b) -> a + b;
                ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
                os.writeObject(lambda);
                os.flush();
            }
        }
    }
    
    static class Client2 {
        public static void main(String[] args) throws IOException {
            try(Socket s = new Socket("127.0.0.1", 8080)){
                Lambda lambda = (Lambda & Serializable) (a, b) -> a - b;
                ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
                os.writeObject(lambda);
                os.flush();
            }
        }
    }
    
    static class Client3 {
        public static void main(String[] args) throws IOException {
            try(Socket s = new Socket("127.0.0.1", 8080)){
                Lambda lambda = (Lambda & Serializable) (a, b) -> a * b;
                ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
                os.writeObject(lambda);
                os.flush();
            }
        }
    }
}
  • 上面的例子做了一些简单的扩展,可以看到不同的客户端可以上传自己的计算规则

P.S.

  • 大部分文献都说 lambda 是匿名函数,但我觉得需要在这个说法上进行补充
  • 至少在 java 里,虽然 lambda 表达式本身不需要起名字,但不得提供一个对应接口嘛

3.2 行为参数化

已知学生类定义如下

static class Student {
    private String name;
    private int age;
    private String sex;

    public Student(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

针对一组学生集合,筛选出男学生,下面的代码实现如何,评价一下

public static void main(String[] args) {
    List<Student> students = List.of(
            new Student("张无忌", 18, "男"),
            new Student("杨不悔", 16, "女"),
            new Student("周芷若", 19, "女"),
            new Student("宋青书", 20, "男")
    );

    System.out.println(filter(students)); // 能得到 张无忌,宋青书
}

static List<Student> filter(List<Student> students) {
    List<Student> result = new ArrayList<>();
    for (Student student : students) {
        if (student.sex.equals("男")) {
            result.add(student);
        }
    }
    return result;
}

如果需求再变动一下,要求找到 18 岁以下的学生,上面代码显然不能用了,改动方法如下

static List<Student> filter(List<Student> students) {
    List<Student> result = new ArrayList<>();
    for (Student student : students) {
        if (student.age <= 18) {
            result.add(student);
        }
    }
    return result;
}

System.out.println(filter(students)); // 能得到 张无忌,杨不悔

那么需求如果再要变动,找18岁以下男学生,怎么改?显然上述做法并不太好… 更希望一个方法能处理各种情况,仔细观察以上两个方法,找不同。

不同在于筛选条件部分:

student.sex.equals("男")

student.age <= 18

既然它们就是不同,那么能否把它作为参数传递进来,这样处理起来不就一致了吗?

static List<Student> filter(List<Student> students, ???) {
    List<Student> result = new ArrayList<>();
    for (Student student : students) {
        if (???) {
            result.add(student);
        }
    }
    return result;
}

它俩要判断的逻辑不同,那这两处不同的逻辑必然要用函数来表示,将来这两个函数都需要用到 student 对象来判断,都应该返回一个 boolean 结果,怎么描述函数的长相呢?

interface Lambda {
    boolean test(Student student);
}

方法可以统一成下述代码

static List<Student> filter(List<Student> students, Lambda lambda) {
    List<Student> result = new ArrayList<>();
    for (Student student : students) {
        if (lambda.test(student)) {
            result.add(student);
        }
    }
    return result;
}

好,最后怎么给它传递不同实现呢?

filter(students, student -> student.sex.equals("男"));

以及

filter(students, student -> student.age <= 18);

还有新需求也能满足

filter(students, student -> student.sex.equals("男") && student.age <= 18);

这样就实现了以不变应万变,而变换即是一个个函数对象,也可以称之为行为参数化

3.3 延迟执行

在记录日志时,假设日志级别是 INFO,debug 方法会遇到下面的问题:

  • 本不需要记录日志,但 expensive 方法仍被执行了
static Logger logger = LogManager.getLogger();

public static void main(String[] args) {
    System.out.println(logger.getLevel());
    logger.debug("{}", expensive());
}

static String expensive() {
    System.out.println("执行耗时操作");
    return "结果";
}

改进方法1:

if(logger.isDebugEnabled())
    logger.debug("{}", expensive());

显然这么做,很多类似代码都要加上这样 if 判断,很不优雅

改进方法2:

在 debug 方法外再套一个新方法,内部逻辑大概是这样:

public void debug(final String msg, final Supplier<?> lambda) {
    if (this.isDebugEnabled()) {
        this.debug(msg, lambda.get());
    }
}

调用时这样:

logger.debug("{}", () -> expensive());

expensive() 变成了不是立刻执行,在未来 if 条件成立时才执行

3.4 函数对象的不同类型

Comparator<Student> c = 
    			(Student s1, Student s2) -> Integer.compare(s1.age, s2.age);
        
BiFunction<Student, Student, Integer> f = 
                (Student s1, Student s2) -> Integer.compare(s1.age, s2.age);
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值