Java 8 函数式编程:Lambda 表达式和方法引用

在很多其他语言中,函数是一等公民。例如 JavaScript 中,函数(Function)和字符串(String)、数字(Number)、对象(Object)等一样是一种数据类型。可以这样定义函数:

var myFunction = function () {
    doSomething();
};

也可以将函数作为参数

setTimeout(function() { 
    doSomething(); 
}, 1000);

在 Java 中,函数不是一等公民。如果想要像其他语言一样定义一个函数,只能通过定义一个接口来实现,例如 Runnable

在 Java 8 之前,可以通过匿名类的方式来创建 Runnable

Thread thread = new Thread(new Runnable() {
    public void run() {
        doSomethong();
    }
});
thread.start();

Java 8 中可以通过 lambda 表达式来创建:

Thread thread = new Thread(() -> doSomethong());
thread.start();

也就是:

Runnable runnable = new Runnable() {
    public void run() {
        doSomethong();
    }
};

简化成了:

Runnable runnable = () -> doSomethong();

是不是看起来像 JavaScript 的函数定义:

var myFunction = function () {
    doSomething();
};

@FunctionalInterface

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method.

@FunctionalInterface
注解用于表明一个接口是函数式接口(functional interface)。函数式接口必须有且只有一个抽象方法。

例如 java.lang.Runnable
就是一个函数式接口,有且仅有一个抽象方法 run
Runnable
源码中就有加上注解 @FunctionalInterface

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

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

函数式接口实例可以通过 lambda 表达式、方法引用(method reference)、构造方法引用(constructor reference)的方式来创建。

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

编译器会把满足函数式接口定义(有且只有一个抽象方法)的任何接口视为函数式接口 ,无论有没有 @FunctionalInterface
注解。

以上两条总结一下:当一个接口符合函数式接口定义(有且只有一个抽象方法),那么就可以通过 lambda 表达式、方法引用的方式来创建,无论该接口有没有加上 @FunctionalInterface
注解。

下面列出一些 Java 中的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator
  • java.util.function
    包下的函数式接口,例如 Predicate
    Consumer
    Function
    Supplier

java.util.function

java.util.function
包下,定义了大量的函数式接口,每个接口都有且只有一个抽象方法,这些接口的区别在于其中的抽象方法的参数和返回值不同。

类型 参数个数 参数类型 返回值类型
Function<T,R> 1 T R
IntFunction<R> 1 int R
LongFunction<R> 1 long R
DoubleFunction<R> 1 double R
ToIntFunction<T> 1 T int
ToLongFunction<T> 1 T long
ToDoubleFunction<T> 1 T double
IntToLongFunction 1 int long
IntToDoubleFunction 1 int double
LongToIntFunction 1 long int
LongToDoubleFunction 1 long double
DoubleToIntFunction 1 double int
DoubleToLongFunction 1 double long
BiFunction<T,U,R> 2 T,U R
ToIntBiFunction<T,U> 2 T,U int
ToLongBiFunction<T,U> 2 T,U long
ToDoubleBiFunction<T,U> 2 T,U double
UnaryOperator<T> 1 T T
IntUnaryOperator 1 int int
LongUnaryOperator 1 long long
DoubleUnaryOperator 1 double double
BinaryOperator<T> 2 T,T T
IntBinaryOperator 2 int,int int
LongBinaryOperator 2 long,long long
DoubleBinaryOperator 2 double,double double
Consumer<T> 1 T void
IntConsumer 1 int void
LongConsumer 1 long void
DoubleConsumer 1 double void
BiConsumer<T,U> 2 T,U void
ObjIntConsumer<T> 2 T,int void
ObjLongConsumer<T> 2 T,long void
ObjDoubleConsumer<T> 2 T,double void
Supplier<T> 0 - T
BooleanSupplier 0 - boolean
IntSupplier 0 - int
LongSupplier 0 - long
DoubleSupplier 0 - double
Predicate<T> 1 T boolean
IntPredicate 1 int boolean
LongPredicate 1 long boolean
DoublePredicate 1 double boolean
BiPredicate<T,U> 2 T,U boolean

Lambda 表达式

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you’re usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.

当一个接口中只有一个方法时(即满足函数式接口定义),此时通过匿名类的语法来编写代码显得比较笨重。使用 lambda 表达式可以将功能作为参数,将代码作为数据。

一个 Lambda 表达式分为以下三个部分:

->

下面举几个例子:

  1. 定义一个函数式接口对象,用于求两个 int 之和,包含两个 int 类型参数 x
    y
    ,返回 x + y
    的值:

    IntBinaryOperator sum = (x, y) -> x + y;
    

  2. 定义一个函数式接口对象,无参数,返回42:

    IntSupplier intSupplier = () -> 42;
    

  3. 定义一个函数式接口对象,用于输出字符串,包含一个 String 类型的参数 s
    ,无返回值:

    Consumer<String> stringConsumer = s -> {
        System.out.println(s);
    };
    

方法引用(Method Reference)

You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it’s often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

如果 lambda 表达式只是调用一个已有的方法,那么可以直接使用方法引用。

例如输出 List
中的元素,用 lambda 表达式:

List<String> list = Arrays.asList("1", "22", "333");
list.forEach(s -> System.out.println(s));

改用方法引用更加简洁:

List<String> list = Arrays.asList("1", "22", "333");
list.forEach(System.out::println);

也就是:

Consumer<String> stringConsumer = s -> System.out.println(s);

简化成了:

Consumer<String> stringConsumer = System.out::println; // 将一个已有的方法赋值给一个函数式接口对象

方法引用有以下几种类型:

  1. 类名::静态方法
    : 静态方法引用

    例如定义一个 max
    函数式接口对象,用于求两个 int 中的最大值:

    IntBinaryOperator max = Math::max;
    

    IntBinaryOperator
    表示有两个 int 参数且返回值为 int 的函数, Math.max()
    静态方法符合要求。

  2. 对象名::非静态方法
    : 对象的方法引用

    例如定义一个 println
    函数式接口对象,用于输出字符串:

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

    Consumer<String>
    表示有一个 String 类型参数且无返回值的函数, System.out.println()
    方法符合要求。

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

    例如定义一个 createHashMap
    函数式接口对象,用于创建一个 HashMap

    Supplier<HashMap> createHashMap = HashMap::new;
    

    Supplier<HashMap>
    表示有一个无参数且返回值为 HashMap 的函数, HashMap
    的构造函数符合要求。

  4. 类名::非静态方法名
    : 文档中解释为:Reference to an instance method of an arbitrary object of a particular type 。如果不理解的话,下面举个例子来说明一下。

    定义一个 concat
    函数式接口对象,用于拼接两个字符串:

    BinaryOperator<String> concat = String::concat;
    

    BinaryOperator<String>
    表示有两个 String 类型参数且返回值为 String 的函数。注意 String 类的 concat
    不是静态方法,且 String.concat(String str)
    只有一个参数,看似不符合要求。实际上它相当于:

    BinaryOperator<String> concat = (s1, s2) -> s1.concat(s2);
    

    即调用第一个参数 s1
    concat
    方法,传入参数 s2

原文 

https://xxgblog.com/2020/04/22/java-8-lambda/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java 8 函数式编程:Lambda 表达式和方法引用

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址