Java8 lambda表达式、函数式接口、方法引用

时间:2021-08-03 19:04:08

一、函数式接口

什么是函数式接口呢?

函数式接口是Java8新增加的内容。如果一个接口只有一个抽象方法,那么该接口就是函数式接口。

我们知道,在Java8以前的版本,接口里面的方法都是抽象的方法,如果接口里只有一个抽象方法,那么该接口就是函数式接口。而在Java8中,接口中不仅仅只有抽象方法了。除了抽象方法,接口中还可以有静态方法和默认方法。例如Comparator接口,Java8以前只有一个 int compare(T o1, T o2); 方法,现在在里面增加了很多的静态方法和默认方法。
下面介绍下静态方法和默认方法,以及抽象方法。

  • 接口静态方法:需要在接口中对方法进行实现。跟普通类的静态方法一样,可以直接通过 接口名.方法名 对方法直接调用。
  • 接口默认方法:需要在接口中对方法进行实现,所有接口的实现类都默认继承该方法,当然子类也可以对该方法进行重写。
  • 接口抽象方法:即没有在接口中实现的方法。这里要注意,如果一个接口声明了java.lang.Object 中的public方法,那么,这些方法不会被算进接口抽象方法的归类。比如说toString方法。

我想Java8中对接口这么改造,一是处于对以前版本的兼容,二是为了Java8函数式编程的新的特性,三是接口中增加方法实现,让开发者可以直接调用,使编程更加的便捷简单。

FunctionalInterface注解

如果一个接口符合函数式接口的定义,那么我们就可以在该接口上面声明FunctionalInterface注解了,用来标示该接口是一个函数式接口,并按照函数式接口的规范在编译的时候对该接口进行检查。

如果我们在某个接口上声明了FunctionalInterface注解,那么编译器会按照函数式接口的定义来要求该接口。

如果某个接口只有一个抽象方法,但我们并没有给该接口声明FunctionalInterface注解,那么编译器依旧会将该接口看做是函数式接口。

二、Lambda表达式

上面介绍了Java8函数式接口,那么什么是Lambda表达式呢?Lambda表达式跟函数式接口又有什么关联呢?

在函数式编程语言当中,函数而不是对象,被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。

在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。所以以前用匿名内部类表示的现在都可以用Lambda表达式来写。

Lambda表达式格式

(Object… args) -> expression
或者
(Object… args) -> {函数式接口抽象方法实现逻辑}

这里用一个Object的可变参数表示。()里面参数的个数,根据函数式接口里面抽象方法的参数个数来决定。

例如,Runnable接口的run方法没有参数,那就只要使用一个()来表示就好了,下面是代码实例:

Runnable thread = () -> System.out.println("hello world");

上面代码表示的意思是,定义一个线程,这个线程做的事情是打印”hello world”。上面的代码还可以看出,如果后面的逻辑只有一行,可以省略{}。也可以看出Lambda表达式其实就是函数式接口的一个实例。

如果函数式接口里面的抽象方法的参数有多个,那么Lambda表达式()里面的参数也要写多个,一般参数的类型可以根据抽象方法定义的参数类型推断出,因此参数前面的类型可以省略,如果不能推断出,参数前面需要加上参数的类型。下面看一个多参数的Lambda表达式的例子:

Function<Integer, Integer> function = (a) ->  2 * a;
System.out.println(function.apply(3));

上面的代码表示的是定义了一个Function 的实例,做的事情是将传入的参数乘以二再返回。

BiFunction<Integer, Integer, Integer> biFunction = (a, b) -> a * b;
System.out.println(function.apply(3, 5));

上面的代码是两个参数的例子。

以上就是Java8 Lambda表达式的格式。

三、方法引用

方法引用也是Java8新增加的一个概念。它能够使代码更加的简洁。

那么什么是方法引用呢?

其实,方法引用就是Lambda表达式,就是函数式接口的一个实例,可以认为是Lambda表达式的一个语法糖。但是,并不是所有的Lambda表达式都可以使用方法引用来表示,需要满足一定的条件(抽象接口的实现需满足一定的条件),才能使用方法引用表示。

我们可以把方法引用理解为方法的指针,指向的是另外一个方法。也就是说,如果抽象方法的实现恰好可以使用调用另外一个方法来实现,就有可能可以使用方法引用。注意是有可能。下面就对方法引用来进行介绍。

方法引用总共分为4类:

1. 类名::静态方法名

如果函数式接口的抽象方法的实现刚好可以通过调用一个静态方法来实现,那么就可以使用该类型的方法引用。例如,一个Integer类型集合,现在需要把它转换成对应的String类型的集合,就可以使用下面的代码:

List<Integer> list = Arrays.asList(1, 2, 3, 4);
List<String> strList = list.stream()
.map(String::valueOf)
.collect(Collectors.toList());

上面的map方法参数是Function类型的,把Integer类型转换成对应的String类型,Java中已经有现成的静态方法String.valueOf(int i)可以实现了,因此可以使用方法引用。更加的简洁。

2. 对象名::实例方法名

如果函数式接口的抽象方法的实现刚好可以通过调用一个实例的实例方法来实现,那么就可以使用该类型的方法引用。下面给出一个例子:
新建一个类:

public class Student {

private String name;

private int score;

public Student(String name, int score) {
this.name = name;
this.score = score;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getScore() {
return score;
}

public void setScore(int score) {
this.score = score;
}

public int compareByScore(Student student1, Student student2) {
return student1.getScore() - student2.getScore();
}

public int compareByName(Student student1, Student student2) {
return student1.getName()
.compareToIgnoreCase(student2.getName());
}
}

然后定义一个Student集合,让集合按照学生名字进行排序:

Student student1 = new Student("zhangsan", 10);
Student student2 = new Student("lisi", 90);
Student student3 = new Student("wangwu", 50);
Student student4 = new Student("zhaoliu", 40);

List<Student> students = Arrays.asList(student1, student2, student3, student4);
students.sort(student1::compareByName);

这里因为Comparator接口的compare方法刚好可以使用Student实例的compareByName方法来实现,因此可以使用方法引用。

3. 类名::实例方法名

如果函数式接口的抽象方法的实现刚好可以由这样一个实例方法的调用来实现:抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果抽象方法的实现能由上面说的实例方法调用来实现的话,那么就可以使用该类型的方法引用。即调用的是Lambda表达式第一个参数的实例方法,Lambda表达式第一个参数以外的参数按顺序作为实例方法的参数传进去完成调用。

下面给出代码实例:

public class Student {

private String name;

private int score;

public Student(String name, int score) {
this.name = name;
this.score = score;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getScore() {
return score;
}

public void setScore(int score) {
this.score = score;
}

public int compareByScore(Student student) {
return this.getScore() - student.getScore();
}

public int compareByName(Student student) {
return this.getName()
.compareToIgnoreCase(student.getName());
}

然后定义一个Student集合,让集合按照学生名字进行排序:

Student student1 = new Student("zhangsan", 10);
Student student2 = new Student("lisi", 90);
Student student3 = new Student("wangwu", 50);
Student student4 = new Student("zhaoliu", 40);

List<Student> students = Arrays.asList(student1, student2, student3, student4);
students.sort(Student::compareByName);

其实就是调用了compare方法的第一个参数Student实例的compareByName方法,第二个参数作为compareByName方法的参数,来当做compare方法的实现逻辑。

4. 构造方法引用: 类名::new

如果函数式接口的抽象方法的实现刚好可以通过调用一个类的构造方法来实现,那么就可以使用该类型的方法引用。

代码实例:

public class MethodReferenceTest {

public String getString(Supplier<String> supplier) {
return supplier.get() + "test";
}

public static void main(String[] args) {
MethodReferenceTest test = new MethodReferenceTest();
System.out.println(test.getString(String::new));
}
}

上面代码的意思是将 new String() 当做Supplier get方法的实现逻辑。很显然,结果输出为test。

四、Java8 新增的常用函数式接口

1. Consumer

Consumer接口在集合遍历中用的比较多。

@FunctionalInterface
public interface Consumer<T> {

void accept(T t);

default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

表示输入一个值,执行逻辑过后不返回值。

2. Function

这个在Stream的map操作中用的比较多。

@FunctionalInterface
public interface Function<T, R> {

R apply(T t);

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
return t -> t;
}
}

表示的是输入一个值,执行一定的逻辑后返回一个值。

3. Predicate

这个在Stream的filter操作中用的比较多。

@FunctionalInterface
public interface Predicate<T> {

boolean test(T t);

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

default Predicate<T> negate() {
return (t) -> !test(t);
}

default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}

表示的是输入一个值,执行一定的逻辑后返回一个布尔值。

4. Supplier

这个在Collectors中使用使用的比较多。

@FunctionalInterface
public interface Supplier<T> {

T get();
}

表示的是不出入值,返回一个值。

除了以上介绍的,还有它们的Bi类型, 对应的原生类型对应引用类型方法引用(LongFunction, DoubleSupplier等。)。可以参考JDK文档来进行使用。