Java8之Lambda表达式

什么是Lambda表达式?

Lambda表达式是一个匿名函数,即没有函数名的函数。它用一个表达式提供了一个实现单个接口方法(函数式接口)的简洁明了的方式,经常被用作匿名内部类的替代。使用Lambda表达式可以写出更简洁灵活的代码。

在了解Lambda表达式之前,我们必须先了解函数式接口

函数式接口可以理解为只有一个抽象方法的接口,例如:


interface {
void accept();
}

其中@FunctionalInterface声明了该接口为函数式接口,若此时再添加一个抽象方法,将编译报错。

Lambda表达式依赖于函数式接口。

为什么使用Lambda表达式?

我们先来看下传统的匿名内部类,使用Comparator接口比较两个整数的大小:

public void test() {
Comparator<Integer> comp = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
}

最关键的代码其实只有return Integer.compare(o1, o2);这一句,用Lambda表达式进行改造:

public void test() {
Comparator<Integer> comp = (o1, o2) -> Integer.compare(o1, o2);
}

还可以再进一步简化:

public void test() {
Comparator<Integer> comp = (o1, o2) -> Integer::compare;
}

可见,使用Lambda表达式可以使代码变得非常简洁,提高可读性,是JDK8非常强大好用的特性,满分推荐!

Lambda表达式语法

参数列表 + 操作符(箭头操作符) + Lambda体,如:

Lambda表达式语法

语法格式

  • 无参数,无返回值

    () -> System.out.println("Hello");
  • 有一个参数,无返回值

    (a) -> System.out.println(a);
    // a -> System.out.println(a);

    当只有一个参数时可以省略参数列表的小括号。同样,当Lambda体中只有一条语句时,return也可以省略。另外,参数列表中也不需写数据类型。这是因为JVM编译器会根据上下文进行类型推断

  • 有两个或以上参数,有返回值,同时Lambda体中有多条语句,此时Lambda体需用大括号

    Comparator<Integer> comp = (o1, o2) -> {
    System.out.println("比较两个整数大小");
    return Integer.compare(o1, o2);
    };

内置函数式接口

上文提到Lambda表达式依赖于函数式接口,不难理解,倘若一个接口中存在多个抽象方法,那么Lambda如何知道使用哪一个方法呢?这是不合理的。

函数式接口代表的一种契约, 一种对某个特定函数类型的契约。 在它出现的地方,实际期望一个符合契约要求的函数。 Lambda表达式不能脱离上下文而存在,它必须要有一个明确的目标类型,而这个目标类型就是某个函数式接口。

既然如此,使用Lambda表达式时我们就需要函数式接口这样一个目标类型,而JDK8内置了一些函数式接口,涵盖了绝大部分使用场景。通常在开发时,我们不需要自己定义函数式接口,直接使用JDK提供的就行了。JDK8内置了四大核心函数式接口。

(图源见水印)

我们可以根据不同的场景去选择使用哪一个接口。例如:

  1. 无返回值,此时可以选择消费型接口


    public void test() {
    printValue(8, (v) -> System.out.println("Value is " + v)); // Value is 8
    }

    // 打印参数值
    public void printValue(int value, Consumer<Integer> con) {
    con.accept(value);
    }
  2. 返回特定类型的对象,此时可以选择供给型接口

    // Supplier<T> 供给型接口
    public void test() {
    int len = getStringLength(() -> (new String("Hello")));
    System.out.println(len); // 5
    }

    public int getStringLength(Supplier<String> sup) {
    return sup.get().length(); // 返回字符串的长度,sup.get()返回String类型的对象
    }
  3. 指定输入参数的类型,返回特定类型的结果,此时可以选择函数型接口

    // Function<T, R> 函数型接口
    public void test() {
    Integer num = strToInt("888", (str) -> Integer.parseInt(str));
    System.out.println(num instanceof Integer); // true
    }

    // 将字符串数值转换为Integer类型
    public Integer strToInt(String s, Function<String, Integer> fun) {
    return fun.apply(s);
    }
  4. 判断指定类型的对象是否满足某个条件,此时可以选择断言型接口

    大专栏  Java8之Lambda表达式d class="code">
    // Predicate<T> 断言型接口
    public void test() {
    boolean result = isEmptyString("", (str) -> "".equals(str));
    System.out.println(result); // true
    }

    // 判断字符串是否为空字符串
    public boolean isEmptyString(String s, Predicate<String> pre) {
    return pre.test(s);
    }

另外,JDK8基于四大函数式接口提供了其他扩展接口,可以满足不同的需求。

(图源见水印)

方法引用

在介绍为什么使用Lambda表达式时,我们将Comparator<Integer> comp = (o1, o2) -> Integer.compare(o1, o2);语句进一步简化为Comparator<Integer> comp = (o1, o2) -> Integer::compare;这便是方法引用。

方法引用允许提前定义的静态或着实例方法去绑定到一个合适的函数式接口来当作参数传递,而不是用一个匿名的lambda表达式。方法引用是对Lambda表达式符合某种情况下的一种缩写,使得Lambda表达式更加的精简,可以理解为Lambda表达式的另一种表现形式。

若Lambda体中已经有了方法实现,并且该方法的参数列表以及返回值类型与接口中的抽象方法的参数列表和返回值类型保持一致,此时就可以使用方法引用。这么说可能有点绕,我们举个例子来看看。

语法格式:

  • 对象名::实例方法名
  • 类名::静态方法名
  • 类名::实例方法名
  1. 以对象名::实例方法名格式为例
public void test() {
// con1 <=> con2 <=> con3
Consumer<String> con1 = (x) -> System.out.println(x);

PrintStream ps = System.out;
Consumer<String> con2 = (x) -> ps::println;

Consumer<String> con3 = System.out::println;
}

由于println是已经实现好的方法,另外,println(String x)方法有一个参数,返回值类型void,而Consumer接口的抽象方法也有一个参数,返回值类型void,因此条件成立,可以使用对象名::实例方法名的格式简化Lambda表达式。

public void println(String x) {
......
}

// Consumer<T>接口的抽象方法
// void accept(T t);
  1. 类名::静态方法名
// 类名::静态方法名
public void test() {
Comparator<Integer> com = Integer::compare; // (o1, o2) -> Integer.compare(o1, o2);
}
  1. 类名::实例方法名,当Lambda体中方法的第一个参数是方法的调用者,第二个参数是方法参数,就可以使用类名::实例方法名格式
// 类名::实例方法名
public void test() {
BiPredicate<String, String> bp = String::equals; // (s1, s2) -> s1.equals(s2);
}

构造器引用

构造器引用类似方法引用,不同的是构造器引用中方法名是new。语法格式:类名::new

对于拥有多个构造器的类,选择使用哪个构造器取决于上下文。

// Person类
public class Person {
private String name;
private int age;

public Person() {}

public Person(String name) {this.name = name;}

public Person(String name, int age) {this.name = name; this.age=age;}

// 此处省略setter和getter等方法
}

// 构造器引用
public void test() {
Supplier<Person> sup = Person::new; // () -> new Person()
System.out.println(sup.get()); // Person [name=null, age=0]

Function<String, Person> fun = Person::new; // (x) -> new Person(x)
System.out.println(fun.apply("Anber")); // Person [name=Anber, int=0]

BiFunction<String, Integer, Person> bf = Person:new; // (name, age) -> new Person(name, age)
System.out.println(bf.apply("David", 18)); // Person [name=David, age=18]
}

对于Supplier供给型接口,其抽象方法无参数,因此调用Person类的无参构造。对于Function函数型接口,其抽象方法有一个形参,因此调用Person类的一个参数的构造方法。

数组引用

语法格式:Type[]::new

// 数组引用
public void test() {
Function<Integer, String[]> fun = String[]::new; // (x) -> new String[x]
String[] strs = fun.apply(10);
System.out.println(strs.length()); // 10
}

参考链接

  1. https://www.jianshu.com/p/a01d84c57180
  2. https://blog.csdn.net/xinghuo0007/article/details/78607166
原文地址:https://www.cnblogs.com/lijianming180/p/12262553.html