java函数式编程--lambda表达式详解

函数式编程重点在函数上,在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情最后有什么结果”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”.

1.函数式编程lambda表达式概述

比如: 我要计算: y = 3*x+4,其中x就是输入量 3x+4就是做什么,最后的y就是计算的结果,而函数式思想是尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。

当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达 上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式——搭乘飞机。

2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,让我们不需要关心怎么 做,只需要关心做什么,从此为我们打开了新世界的大门

1.1 冗余的Runnable代码

当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。代码如下:

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:06
 */
public class NoNameInnerClassThread {
    public static void main(String[] args) {
        // 匿名创建了一个Runnable类
       new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("覆盖重写方法,多线程任务执行!");
           }
       }).start();
    }
}

上述代码分析

  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类
  • 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错
  • 而实际上,似乎只有方法体才是关键所在

2. 体验lambda的优化写法

借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:06
 */
public class NoNameInnerClassThread {
    public static void main(String[] args) {
        // 匿名创建了一个Runnable类
//       new Thread(new Runnable() {
//           @Override
//           public void run() {
//               System.out.println("覆盖重写方法,多线程任务执行!");
//           }
//       }).start();
        
        // lambda表达式写法
        new Thread(() -> System.out.println("覆盖重写方法,多线程任务执行!")).start();
    }
}

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。 不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!

3. 语义分析

Lambda是怎样击败面向对象的?在上例中,核心代码其实只是如下所示的内容

()> System.out.println("覆盖重写方法,多线程任务执行!")
  • 前面的一对小括号即 run 方法的参数(无),代表做这件事不需要参数;
  • 中间的一个箭头代表将前面的参数传递给后面的代码;
  • 后面的输出语句即业务逻辑代码;

仔细分析该代码中的语义, Runnable 接口只有一个 run 方法的定义:

 public abstract void run();

制定了一种做事情的方案(其实就是一个函数)

  • 无参数: 不需要任何条件即可执行该方法
  • 无返回值: 该方法不产生任何结果
  • 代码块(方法体): 方法的执行步骤

4. lambda标准格式

(参数类型 参数名称)  ->  {代码语句; return;}
  • 一些参数,一个箭头,一段代码
  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔
  • -> 是新引入的语法格式,代表指向动作(做什么事)
  • 大括号内的语法与传统方法体要求基本一致。

5. 举例lambda用来排序

下面举例演示 java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:

public abstract int compare(T o1, T o2);
  • 传统写法和lambda写法对比
import java.util.Arrays;
import java.util.Comparator;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:38
 */
public class ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {
                new Person("张三", 12),
                new Person("李四", 26),
                new Person("王五", 20)
        };

        /* 传统写法匿名内部类 排序年龄*/
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                // 年龄从小到大排序
                return o1.getAge() - o2.getAge();
            }
        };

        Arrays.sort(array,comp);


        /* lambda 写法排序年龄*/
        Arrays.sort(array,(Person a ,Person b) -> {return a.getAge() - b.getAge();});

        for (Person person : array) {
            System.out.println("lambda: "+person);
        }
    }

}

代码分析 : 下面我们来搞清楚上述代码真正要做什么事情

  • 为了排序, Arrays.sort 方法需要排序规则,即 Comparator 接口的实例,抽象方法 compare 是关键
  • 为了指定 compare 的方法体,不得不需要 Comparator 接口的实现类;
  • 为了省去定义一个 ComparatorImpl 实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象 compare 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 实际上,只有参数和方法体才是关键

6. 自定义计算器接口,使用lambda表达式调用

  • 给定一个计算器 Calculator 接口,内含抽象方法 calc 可以将两个int数字相加得到和值:
/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:53
 */
public interface Calculator {
    int calc(int a, int b);
}

  • 创建测试类
/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:54
 */
public class Lambda {
    public static void main(String[] args) {
        /* lambda 表达式写法*/
        invokeCalc(20,30,(int a, int b) -> {return a+b;});
    }

    public static void invokeCalc(int a, int b, Calculator calculator){
        int result = calculator.calc(a,b);
        System.out.println("结果是: "+result);
    }
}

7. lambda 省略格式

Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-01-13 10:54
 */
public class Lambda {
    public static void main(String[] args) {
        /* lambda 表达式写法*/
        invokeCalc(20,30,( a,  b) ->  a+b);
    }

    public static void invokeCalc(int a, int b, Calculator calculator){
        int result = calculator.calc(a,b);
        System.out.println("结果是: "+result);
    }
}

省略规则

  • 小括号内参数类型可以省略
  • 如果小括号内有且仅有一个参数,则小括号可以省略
  • 如果大括号内有且仅有一个语句,则无论是否需有返回值,都可以省略大括号,return 关键字及分号

8. lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  • 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的 RunnableComparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda
  • 使用Lambda必须具有上下文推断,也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例
  • 备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三胖哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值