1. 函数式编程

强调做什么,而不是以什么形式做。

函数式编程思想和面向对象编程思想的区别:

  • 面向对象:解决某个问题需找到某个类的某个方法完成
  • 函数式:只要能获取到结果,不重视得到结果的过程

什么是函数式编程思维? 毫无干货的带你理解什么是函数式编程

1.1 示例引入

Lambda表达式是是Java8的新特性,通过使用它可以简化代码的编写,但是同样可能会带来阅读性下降,维护性难度增加等问题。Lambda表达式不仅可以直观上简化代码的书写,通过也方便后面函数式接口和方法引用的应用。所以,我们需要了解一下Java中的Lambda表达式,在后面的编程中根据具体的情况选择性使用就好。

紧接上一篇多线程的内容,如果想要创建新线程,一般有两种方式:

  • 通过创建Thread类的子类对象创建
  • 通过实现Runnable接口实现

两种方式都需要重写run()。下面我们看一下之前第二种方法是如何创建新线程的。首先我们需要创建Runnable接口的实现类,并重写run(),设置线程任务。

  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. ...
  5. }

然后在main()中创建一个Runnable接口实现类的对象,及创建Thread类对象,构造方法中传递Runnable接口的实现类对象,最后调用Thread类中的start(),开启新的线程执行run()

  1. public class RunnableMain {
  2. public static void main(String[] args) {
  3. MyRunnable my = new MyRunnable();
  4. Thread t = new Thread(my);
  5. t.start();
  6. }
  7. }

如果想要简化代码编写,我们可以使用匿名内部类而省略实现类的编写:

  1. public class RunnableMain {
  2. public static void main(String[] args) {
  3. new Thread(new Runnable(){
  4. @Override
  5. public void run() {
  6. ...
  7. }
  8. }
  9. }).start();
  10. }
  11. }

这样看起来好像并没有太多的简化,而如果使用Lambda表达式,我们只需要写成下面这样:

  1. public class RunnableMain {
  2. public static void main(String[] args) {
  3. new Thread(() -> {
  4. System.out.println(Thread.currentThread().getName());
  5. }).start();
  6. }
  7. }

通过Lambda表达式,我们大大的简化了代码的编写,它同时需要我们对于代码中的逻辑有更深的理解。

2. 概念

Lambda表达式主要由三部分组成:

  1. (参数列表) -> {一些重写方法的代码}

其中:

  • ():接口中抽象方法的参数列表,没有参数就空;如果有参数,参数之间使用逗号分隔
  • ->:传递的意思,将参数传递给方法体
  • {}:重写接口的抽象方法的方法体

下面我们对比一下使用匿名内部类和使用Lambda表达式创建新线程代码之间的比对:
image-20200430161810778.png

2.1 无参数、无返回值表达式

假设有一个Person接口,接口中只有一个抽象方法show()。如果想要使用这接口,一种方式是单独写接口的实现类,并实现show();另一种方式是使用匿名内部类,同样需要实现show()

  1. public interface Person {
  2. public abstract void show();
  3. }
  1. public class LambdaTest1 {
  2. public static void main(String[] args) {
  3. // 匿名内部类实现
  4. showName(new Person() {
  5. @Override
  6. public void show() {
  7. System.out.println("Forlogen");
  8. }
  9. });
  10. }
  11. public static void showName(Person p){
  12. p.show();
  13. }
  14. }

那么如何通过Lambda表达式实现main()中代码部分呢?根据Lambda表达式的定义,使用()表示public void show()部分,使用{ System.out.println("Forlogen");}表示show()方法的方法体,最后通过->连接两部分即可。如下所示,我们只要一行代码就可以实现上述的逻辑。

  1. showName(() -> {
  2. System.out.println("Forlgoen");
  3. });

2.2 有参数有返回值表达式

回顾之前的内容,如果我们想要使用数组保存一些Student对象,并希望使用Arrays中的sort()根据年龄进行排序,那么该怎么做呢?首先创建Student类:

  1. package Lambda;
  2. public class Student {
  3. private int age;
  4. private String name;
  5. public Student(int age, String name) {
  6. this.age = age;
  7. this.name = name;
  8. }
  9. public int getAge() {
  10. return age;
  11. }
  12. public void setAge(int age) {
  13. this.age = age;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. }

使用数组保存新建的Student对象,并使用Arrays.sort()根据年龄升序进行排列:

import java.util.Arrays;
import java.util.Comparator;

public class LambdaTest2 {
    public static void main(String[] args) {
        Student[] list = {new Student(24, "Forlogen"), new Student(20, "kobe")};

        Arrays.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        for(Student s : list){
            System.out.println(s.getAge() + " " + s.getName());
        }
    }
}

如果使用Lambda表达式重写Comparator接口的实现部分,只需要一行代码:

Arrays.sort(list, (Student o1, Student o2) -> {
    return o1.getAge() - o2.getAge();
});

更省略的方式可写作:

Arrays.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());

2.3 省略格式规则

上面最后一行代码就设计到Lambda表达式中的省略规则。根据函数式编程思想,Lambda表达式强调做什么,而不关注怎么做,因此凡是可以通过上下文推断得出的部分都可以省略。

省略规则

  • 小括号中的参数类型可以省略,如(Student o1, Student o2)可写成(o1, o2)
  • 如果小括号中有且仅有一个参数,那么参数类型和小括号可以省略,因为方法体中未知参数只可能来自参数列表
  • 如果大括号中有且仅有一个语句,那么无论是否有返回值,都可以省略大括号、return关键字和语句分号,而且它们必须一起省略,如{return o1.getAge() - o2.getAge();}可写成o1.getAge() - o2.getAge()

2.4 使用前提

  • 使用Lambda表达式必须具有接口,且要求接口中有且仅有一个抽象方法
  • 使用Lambda表达式必须具有上下文推断,即方法的参数或局部变量必须为Lambda对应的接口类型,才能使用lambda表达式作为该接口的实例

作为对比,我们可以看下python中的lambda表达式和Java中的Lambda表达式有什么相同点和什么共同点,可以帮助我们更好的理解Java中Lambda表达式的意义和使用。

3. Python中的lambda表达式

lambda简化了函数定义的书写形式。是代码更为简洁,但是使用函数的定义方式更为直观,易理解。如果我们想要写一个方法实现对传入的参数加1,然后返回相加后的结果,可以写成:

def g(x):
    return x + 1

然后通过方法名调用,传入参数接收结果。

g(2) # 3

而使用lambda表达式实现上面同样的功能,从而简化了方法的编写:

g = lambda x: x + 1 
g(2) # 3

Python中有几个定义好的如filter(), map(), reduce()等全局函数方便用户使用,在某些内置函数中同样可使用lambda表达式:

  • filter :用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。函数接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
    nums = [1,2,3,4,5,6]
    print (list(filter(lambda x: x % 2 == 0, nums))) # [2, 4, 6]
    


如果采用定义方法的方式实现,如下所示:

def myfilter(nums):
    return [i for i in nums if i % 2 == 0]

print (myfilter(nums)) # [2, 4, 6]
  • map:根据提供的函数对指定的序列做映射,格式为
    map(function,iterable,...)
    


在map函数中使用Lambda表达式

print (list(map(lambda x: x * 2, nums))) # [2, 4, 6, 8, 10, 12]
def mymapper(nums):
    return [i * 2 for i in nums]

print (mymapper(nums)) #  [2, 4, 6, 8, 10, 12]
  • reduce: 对参数序列中元素进行累积,格式为
    reduce(function, iterable[, initializer])
    


函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

print (reduce(lambda x, y: x + y, nums)) # 21


它就等价于:

def add(x, y):
    return x + y

print (reduce(add, nums)) # 21

总结


通过对比Python中的lambda表达式和Java中Lambda表达式,我们可以发现它们的出现都是希望在一定程度上简化代码的书写,同时Java中的Lambda表达式还是其他内容的基础。因此,用户在决定是否使用Lambda表达式已经如何使用,需要根据具体的场景具体分析。