Java8新特性:Lambda表达式与函数式编程

前言

Java8已经发布好久了,说是新特性其实早就不算新特性了,之前一直想补上这块内容,现在来讲讲我对Lambda表达式和函数式编程的理解。

正文

接口作为方法参数

对于大多数Java初学者来说,当第一次遇到接口作为方法参数这样的语法时,是非常困惑的,而且这种语法非常常见,比如我们创建线程时,Thread类的其中一个构造函数竟然以Runnable接口为参数。

public Thread(Runnable target) {
    //...
}

一直以来Java都被认为是一种纯面向对象的语言,“万物皆对象”的思想已经深入人心了。但是随着Java8的发布,函数式编程的思想越来越明显,尤其是Lambda表达式和Stream流的引入,让Java焕发了新的活力,它允许人们可以用函数式编程思维来思考问题。

关键是,这种方式有什么好处呢?官方讲的很专业,但是讲的太专业了,不太好理解。我尝试从个人理解来发表下看法:函数式编程有点像模板模式,函数主体规定了整体流程,变化的部分就在于接口类型的参数在整体流程中的参与度。以上面的Thread类为例,外层的Thread类规定好了想要实例化一个Thread线程对象的具体流程,而变化的是线程具体所做的工作,那么这一部分就被封装在入参的接口类型Runnable中,那么我们每次在新建Thread类时,重点关注变化的部分即可,也即重点实现Runnable接口提供的run()方法。

匿名内部类

Lambda表达式出现之前,函数式编程使用内名内部类来实现,还是以上面的Thread类为例,由于是以接口类型为形参,那么实际运行时要传入一个实际对象,匿名内部类就是实例化了一个对象传给了形参,这个对象重点实现了接口的抽象方法。

new Thread(new Runnable() {
    public void run() {
        System.out.println("hi");
    }
}).start();

Thread类的入参接受一个Runnable的具体实现类对象,并实现了run()方法,也就实现了线程的具体功能。但是我们仔细来看入参这段代码,实际上我们关心的只有run()方法里面的方法内容,其他的都是冗余信息,为了简化语法,于是引入了Lambda表达式。

new Runnable() { // 这一行实际不需要
    public void run() { // 这一行也不需要
        System.out.println("hi"); // 真正需要保留的只有这一行
    }
}

Lambda表达式

Lambda表达式作为一种语法糖,是为了简化匿名内部类,大家千万不要认为其有多高深,熟练掌握使用方法即可。上面我们说过,只需要关注接口中的方法,那么Lambda表达式将冗余的信息都去掉了,只保留了方法的参数列表和方法体。特别需要说明的是,只有接口中有且仅有一个抽象方法,才能使用Lambda表达式。

new Thread(() -> {
    System.out.println("hi");
});

初学者第一次看到这样的语法就很无语,开始我也是很抗拒,认为不如匿名内部类清晰,其实慢慢习惯后就能接受了。

Lambda表达式只保留了接口方法的参数列表和方法体:
1、() 小括号就是参数列表,没有入参就是空括号,有参数可以写成(a,b)这种形式
2、-> 箭头是固定形式,连接参数列表和方法体
3、{} 花括号就是方法体,是真正实现抽象方法的地方,如果方法体只有一行,花括号可以省略,return关键字也可以省略。

再举个例子:列表排序Arrays.sort方法参数列表接收一个Comparator接口实现自定义的排序方式。

String[] array = new String[]{"a", "b", "c"};
Arrays.sort(array, (a, b) -> a.compareTo(b));

看到了吗?Lambda表达式可以将匿名内部类简化成只有一行。Comparator接口中的compare方法有2个入参,那么我们可以指定参数列表(a,b),参数的名字叫什么都可以,随便取。方法体的实现只有一行,那么花括号和return都可以省略。这样简洁的语法糖谁不爱?

函数式编程接口

我们上面说过了,只要一个接口有且仅有一个抽象方法,就可以使用Lambda表达式。于是Java专门弄了一个注解@FunctionalInterface,把它加到接口上一方面起到注释的作用,让人看了就知道这是个函数式接口,另一方面告诉编译器要保障接口中仅有一个抽象方法。

我们可以自定义函数式接口,这里我就不再举例了。Java8给我们提供了一些写好的函数式接口,适用于不同的场景,这些接口在java.util.function包里。我们来看一些常见的。

Supplier接口
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

顾名思义是一个供应接口,接口及抽象方法比较简单,用来得到一个泛型指定类型的对象。比如接口指定了泛型为String,那么get方法返回的只能是String类型。get方法非常简单,意味着可发挥的空间也越大,举个例子:

public static void main(String[] args) {
    int ret = getMax(() -> Math.max(1, 2)); // get方法的Lambda表达式
    System.out.println(ret);
}

public static int getMax(Supplier<Integer> supplier) {
    return supplier.get();
}
Consumer接口

它和Supplier接口正好相反,它不生成数据,而是消费一个数据,具体消费什么类型的数据取决于泛型指定的类型。只列出了简单的accept方法:

public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

来举个例子看看是怎么消费数据的:

public static void main(String[] args) {
    consume("jimmy", (a) -> System.out.println("hello" + a)); // accept方法的lambda表达式
}

public static void consume(String message, Consumer<String> comsumer) {
    comsumer.accept(message);
}
Predicate接口
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

专门用来做推断,具体推断什么类型是由泛型来指定的。举个例子:

public static void main(String[] args) {
    boolean ret = predicate(12, (a) -> a <= 10);
    System.out.println(ret);
}

public static boolean predicate(Integer a, Predicate<Integer> predicate) {
    return predicate.test(a);
}
Function接口
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

可以根据一个类型T得到一个类型R,下面举一个类型转换的例子:

public static void main(String[] args) {
    exchange("123", (a) -> Integer.parseInt(a));
}

public static int exchange(String text, Function<String, Integer> function) {
    return function.apply(text);
}

总结

函数式编程在框架类代码中很常见,尤其是Spring源码中特别多,它的最主要作用就是简化代码,封装变化。后面我们在讲Spring源码时会再次讲到函数式编程。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值