Java什么是函数?函数与方法?

道之伊始

宇宙初开之际,混沌之气笼罩着整个宇宙,一切模糊不清。

然后,盘古开天,女娲造人:日月乃出、星辰乃现,山川蜿蜒、江河奔流、生灵万物,欣欣向荣。此日月、星辰、山川、江河、生灵万物,谓之【对象】,皆随时间而化。

然而:日月之行、星汉灿烂、山川起伏、湖海汇聚,冥冥中有至理藏其中。名曰【道】,乃万物遵循之规律,亦谓之【函数】,它无问东西,亘古不变

作为设计宇宙洪荒的程序员

  • 造日月、筑山川、划江河、开湖海、演化生灵万物、令其生生不息,则必用面向【对象】之手段
  • 若定规则、求本源、追纯粹,论不变,则当选【函数】编程之思想

下面就让我们从【函数】开始。

一、什么是函数

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

数学上:

例如:

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

二、大道无情

无情

何为无情:

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

三、函数与方法

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

函数是

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

而方法是

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

四、不变的好处

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

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

五、合格的函数无状态

大道无形

函数化对象

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

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

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 表达式本身不需要起名字,但不得提供一个对应接口嘛

六、行为参数化

已知学生类定义如下

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);

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

七、延迟执行

在记录日志时,假设日志级别是 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 条件成立时才执行

八、函数对象的不同类型

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);二. 函数编程语法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值