Java 8 之函数式接口和Lambda表达式

Java 8 之函数式接口和Lambda表达式

Symon 1,122 2020-11-01

Java 8函数式接口

有且只有一个抽象方法的接口称为函数式接口。Java 8新增了@FunctionalInterface注解,使用该注解的接口就是函数式接口。

不是使用@FunctionalInterface注解的接口才是函数式接口,使用它是为了检查函数式接口的正确性,并且是一种规范。
例如,我们在一个接口之上使用了该注解,并在其中添加多个抽象方法,此时会引发编译器错误。

java.lang.Runnable就是一个函数式接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Java 8函数式接口的主要好处是我们可以使用Lambda表达式实例化他们,规避使用笨重的匿名类实现。

Java 8 Collections API已被重写,并且引入了包含函数式接口的新Stream API。Java 8在java.util.function定义了很多函数式接口。其中常用的函数式接口有:ConsumerSupplierFunctionPredicate等。

下面是一些代码片段,以便我们更好的理解函数式接口

interface Test {
    boolean equals(Object obj);
}
//Test不是函数式接口,equals是Object对象的一个成员方法

interface Comparator<T> {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}
//Comparator是函数式接口,Comparator只有一个抽象的非Object的方法

interface Test2 {
    int test2();
    Object clone();
}
//Test2不是函数式接口,因为Object.clone()方法不是public而是protected

interface X {
    int test(String str);
}
interface Y {
    int test(String str);
}
interface Z extends X, Y {
}
//Z是函数式接口,继承了两个相同签名相同返回值的方法

interface X {
    List test(List<String> list);
}
interface Y {
    List<String> test(List list);
}
interface Z extends X, Y {
}
//Z是函数式接口,Y.test 是一个 subsignature & return-type-substitutable
//关于subsignature & return-type-substitutable可阅读下面链接
//https://www.ssymon.com/archives/subsignature-return-type-substituable
//这里Y.test签名是X.test签名的subsignature,并且Y.test的返回值类型和X.test返回值类型可以兼容

interface X {
    int test(List<String> list);
}
interface Y {
    int test(List<Integer> list);
}
interface Z extends X, Y {
}
//Z不是函数式接口,两个抽象方法没有一个是subsignature
//虽然方法签名与泛型无关,但X.test和Y.test无法兼容,Z的编译就会出错

interface X {
    long test();
}
interface Y {
    int test();
}
interface Z extends X, Y {
}
//编译出错:methods have unrelated return types
//Z不是函数式接口,两个方法返回值不相关不兼容,没有一个是return-type-substitutable

interface A<T> {
    void test(T arg);
}
interface B<T> {
    void test(T arg);
}
interface C<X, Y> extends A<X>, B<Y> {
}
//编译错误:both methods have same erasure, yet neither overrides the other
//C不是函数式接口,两个方法签名不同,擦除之后变成相同的原生类型

关于 subsignature & return-type-substitutable

Lambda表达式

Lambda表达式是Java 8重要的新功能之一,它是将Java从面向对象引入函数式编程领域的一项努力。

什么是Lambda表达式:Lambda表达式是一个匿名函数,即没有函数名的函数。

在Java中:所有函数都是类的成员,被称为方法。要创建方法,您需要定义其所属的类。

Lambda表达式语法:箭头前面部分是方法的参数列表,后一部分方法主体

(parameters) -> expression
或
(parameters) -> { statements; }

Lambda表达式使得我们可以使用非常简洁的语法定义一个类和单个方法,以实现具有单个抽象方法的接口,即函数式接口

下面我们用一些代码来弄明白Lambda表达式如何简化和缩短代码,并使得代码更具备可读性和可维护性。

实现接口:在Java 8之前,如果要创建线程,首先要定义一个实现可运行接口的类。即函数式接口java.lang.Runnable,其抽象方法run()不接受任何参数。我们需要定义一个实现类来实现它。

public class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("TestRunnable is running");
    }
    public static void main(String[] args) {
        TestRunnable r = new TestRunnable();
        Thread thread = new Thread(r);
        thread.start();
    }
}

在此示例中,我们实现了run()方法将一串字符串打印在控制台。然后创建一个名为 r 的实例对象,并将其传递给线程类的构造函数来创建一个线程对象,并调用线程的start方法。

匿名内部类:我们对上面的代码进行一些改进,使用匿名内部类的方式来实现。

public static void main(String[] args) {
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable inner class is running");
        }
    });
    thread1.start();
}

相比实现接口的方式,匿名内部类的方式更加简洁,无序额外新增实现类。

使用Lambda表达式:在Java 8中,使用Lambda重构的代码更简洁,更具可读性。

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable lambda is running"));
    thread.start();
}

为什么要使用Lambda

1.通过对以上的代码对比我们可以发现使用Lambda表达式可以减少需要编写的代码量以及减少必须创建和维护的自定义类的数量。

如果您要实现一次使用的接口,那么创建另一个代码文件或另一个命名类并不总是很有意义。Lambda表达式可以定义一次匿名实现,以供一次性使用,并显着简化代码。

2.使用Lambda表达式可以将行为传递给方法。

假如我们现在还不了解Stream,我们需要写一个方法过滤(给定条件)苹果,我们可以使用谓词来实现。如下:

public static void main(String[] args) {
    List<Apple> apples = new ArrayList<>();
    apples.add(new Apple("red", 100));
    apples.add(new Apple("blue", 100));
    apples.add(new Apple("red", 110));
    apples.add(new Apple("blue", 110));
    List<Apple> blueApples = filterApple(apples, apple -> apple.getColor().equals("blue"));
    System.out.println(blueApples);
    List<Apple> bigApples = filterApple(apples, apple -> apple.getWeight() > 100);
    System.out.println(bigApples);
}
public static List<Apple> filterApple(List<Apple> appleList, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    if (appleList != null && !appleList.isEmpty()) {
        for (Apple apple : appleList) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
    }
    return result;
}

3.在Stream API中使用
后面学习会继续Stream API

Lambda表达式示例

() -> {}                     // No parameters; void result

() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body

// Complex block body with multiple returns
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1                 // Single inferred-type argument, same as below
x -> x+1                   // Parenthesis optional for single inferred-type case

(String s) -> s.length()   // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length()              // Single inferred-type argument
t -> { t.start(); }          // Single inferred-type argument

(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

方法引用和构造方法引用示例

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new

# java # java8