java函数式编程上下文_Java8 新特性 —— 函数式编程

本文部分摘录自 On Java 8

概述

通常,传递给方法的数据不同,结果也不同。同样的,如果我们希望方法被调用时的行为不同,该怎么做呢?结论是:只要能将代码传递给方法,那么就可以控制方法的行为。

说得再具体点,过去我们总是创建包含所需行为的对象,然后将对象传递给想要控制的方法,一般使用匿名内部类来实现。假设现在有这么一个需求:有一个员工信息列表,根据年龄过滤出符合条件的员工信息

// 过滤出大于35岁的员工public List filterEmployee(List list) { List emps = new ArrayList<>(); for(Employee emp : list) { if(emp.getAge() > 35) { emps.add(emp); } } return emps;}// 过滤出大于45岁的员工public List filterEmployee2(List list) {... }

这样写当然能实现需求,但如果需求变了,要过滤 45 岁的,那岂不是又得写一个 filterEmplyee2() 方法?如果还要过滤 50 岁的,60 岁的,那就没完没了了,而且代码的实现逻辑几乎没有区别。于是我们借助策略模式的思想来简化代码。

public interface MyPredicate<> { boolean predicate(T t);}// 如果有其他过滤需求,只需要实现 MyPredicate 接口即可public class EmployeeFilter implements MyPredicate { @Override public boolean predicate(Employee employee) { return t.getAge() >= 35; }}// 根据传入的 MyPredicate 对象来实现不同的过滤逻辑public List filterEmployee(List list, MyPredicate mp) { List emps = new ArrayList<>(); for(Employee emp : list) { if(mp.predicate(emp)) { emps.add(emp); } } return emps;}public void test(List list) { // 创建实现类对象,传入过滤方法 MyPredicate predicate = new EmployeeFilter<>(); List res = filterEmployee(list, predicate); // 更简单的方式是使用匿名内部类 List res2 = filterEmployee(list, new MyPredicate() { @Override public boolean predicate(Employee employee) { return t.getAge() >= 100; } });}

通过观察我们发现,我们需要的只有 predicate() 方法的代码,其他的我们一律不关心。如果 MyPredicate 接口还有其他抽象方法,我们又必须每一个做一次实现,但真正用上的只有 predicate() 方法,不仅显得冗余,而且可读性也很低。为了解决这个问题,Java8 为我们提供了 Lambda 表达式和方法引用两种更加简洁的方式。

Lambda 表达式

Lambda 表达式是一个匿名函数,可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样传递)。虽然在 JVM 规范规定一切都是类,但其幕后执行的各种操作使得 Lambda 看起来像是函数。因此我们可以大胆假设 Lambda 表达式产生的就是一个函数,而不是类。

Lambda 的基本语法有是:(参数) -> {方法体}

其中 -> 可以视为将参数传递给方法体使用的一个中间桥梁

左侧为表达式的参数列表。使用括号包裹参数,当只有一个参数时,可以不需要括号,如果没有参数,则必须使用括号表示空参数列表。参数列表的数据类型可以省略不写,因为 Java 的编译器可以帮助我们根据上下文推断数据类型

右侧为表达式中所需执行的功能。方法体如果只有单行,可以省略花括号,此时执行结果自动转化为 Lambda 表达式的放回值,使用 return 关键字是非法的;如果方法体有多行,则必须放在花括号中,这时如果有返回值,就需要使用 return

Lambda 表达式能产生比匿名内部类更易读的代码,因此我们应该尽可能使用 Lambda 表达式。回到之前的例子,我们可以用 Lambda 表达式来替换匿名内部类。

public interface MyPredicate<> { boolean predicate(T t);}// 根据传入的 MyPredicate 对象来实现不同的过滤逻辑public List filterEmployee(List list, MyPredicate mp) { List emps = new ArrayList<>(); for(Employee emp : list) { if(mp.predicate(emp)) { emps.add(emp); } } return emps;}public void test(List list) { // 使用 Lambda 表达式 List res = filterEmployee(list, e -> e.getAge() <= 5000);}

Lambad 表达式通常比匿名内部类产生更易读的代码,因此我们应该尽可能使用 Lambda 表达式。

如果我们想编写递归的 Lambda 表达式,必须注意:

方法引用

Lambda 表达式可以帮助我们实现仅调用方法,而不做其他多余动作(如创建对象)的目的,而有些情况下,已经存在能满足需求的方法,我们可以不必再编写 Lambda 表达式,而通过方法引用直接使用该方法。可以理解为方法引用是 Lambda 表达式的另一种表现形式。

方法引用的组成:类名或对象名,后面跟 ::,然后跟方法名称,如果要分类的话,可以用如下组合:

引用静态方法 className::staticMethod

引用某个对象的实例方法 instance::instanceMethod

引用某个类型的任意对象的实例方法 className::instanceMethod

引用构造方法 className::new

interface Callable {void call(String s);}class Describe { void show(String msg) { System.out.println(msg); }}public class MethodReferences { static void hello(String name) { System.out.println("Hello, " + name); } public static void main(String[] args) {// 对象名:: 方法名称 Describe d = new Describe(); Callable c = d::show; c.call("call()");// 类名::方法名 c = MethodReferences::hello; c.call("Bob"); }}

要注意的是,方法引用的签名(参数类型和返回类型)必须符合 Callable 的 call() 的签名。上述代码我没有演示 className::instanceMethod 和 className::new 的情况,这两个有点特殊,待会再介绍。

Runnable 接口

通过之前的学习,我们发现 Runnable 接口也符合特殊的单方法接口格式:它的 run() 方法不带参数,也没有返回值,因此我们可以使用 Lambda 表达式和方法引用作为 Runnable

class Go { static void go() { System.out.println("thread go"); }}public class RunnableMethodReference { public static void main(String[] args) {// 匿名内部类方式 new Thread(new Runnable() { public void run() { System.out.println("Anonymous"); .........

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值