Java8新特性:Lambda表达式函数式接口以及方法引用

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8新特性:Lambda表达式函数式接口以及方法引用相关的知识,希望对你有一定的参考价值。

文章目录

Java8新特性:Lambda表达式

1. Lambda表达式概念

首先 Lambda 表达式是Java 8引入的重要新特性。

Lambda 是一个匿名函数(Lambda 表达式简化了匿名内部类的形式,并且可以达到同样的效果,当然Lambda 要优雅得多),我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。

lambda表达式与匿名函数虽然最终达到的效果是一样的,但其底层实现原理却并不相同:

  • 匿名内部类在编译之后会创建一个新的匿名内部类出来
  • 而 Lambda 是调用 JVM invokedynamic指令实现的,并不会产生新类。

还有一个重点:Lambda表达式返回的是接口对象实例

其基本语法如下:

(参数列表) -> 
	方法体
;

其中->是Lambda运算符,它的英文名是goes to,嗯,就是转到的意思

整个的lambda表达式等价于:

new 接口名() 
    // 实现接口的方法
    @Override
    public void method() 
       	// 函数逻辑
    
;

看下面以Lambda语法创建线程和匿名内部类创建线程的例子

package com.yyl.lambda;

public class Test 
    public static void main(String[] args) 
        // 用匿名内部类的方式来创建线程
        new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("公众号:毒力舌---点关注,不迷路!");
            
        ).start();


        // 使用Lambda来创建线程
        new Thread(() -> System.out.println("公众号:毒力舌---点关注,不迷路!")).start();
    

结果是一样的:

2. Lambda表达式语法

接口中函数的声明总共6种情况:

  • 接口方法无返回值有返回值分2种
  • 其中无参数单个参数多个参数又分3种情况。

两两结合,2×3=6,一种有六种

如下六种函数示例代码:

interface I01 
    void method();

interface I02 
    void method(int a);

interface I03 
    void method(int a, int b);

interface I04 
    int method();

interface I05 
    int method(int a);

interface I06 
    int method(int a, int b);

我们使用lambda表达式重写这几个方法并运行:

public class Test 
    public static void main(String[] args) 
        I01 i01 = () -> 
            System.out.println("无返回值、无参数");
        ;
        I02 i02 = (int a) -> 
            System.out.println("无返回值,单个参数。a=" + a);
        ;
        I03 i03 = (int a, int b) -> 
            System.out.println("无返回值,多个参数。a=" + a + ",b=" + b);
        ;
        I04 i04 = () -> 
            System.out.println("有返回值、无参数");
            return 4;
        ;
        I05 i05 = (int a) -> 
            System.out.println("有返回值,单个参数。a=" + a);
            return 5;
        ;
        I06 i06 = (int a, int b) -> 
            System.out.println("有返回值,多个参数。a=" + a + ",b=" + b);
            return 6;
        ;
        i01.method();
        i02.method(5);
        i03.method(5,10);
        System.out.println(i04.method());
        System.out.println(i05.method(5));
        System.out.println(i06.method(5, 10));
    

输出:

无返回值、无参数
无返回值,单个参数。a=5
无返回值,多个参数。a=5,b=10
有返回值、无参数
4
有返回值,单个参数。a=5
5
有返回值,多个参数。a=5,b=10
6

看完以上代码已经能大概了解lambda表达式的用法了。

不过细心的你已经发现,要是这些接口里面定义的方法不止一个呢?那岂不是无法确定Lambda表达式究竟是要实现哪一个接口方法?

这里就要提到函数式接口的概念

3. 函数式接口(Functional Interface)

Java 8为了使现有的函数更加友好地支持Lambda表达式,引入了函数式接口的概念。

函数式接口本质上是一个仅有一个抽象方法的普通接口,所以又叫SAM接口(Single Abstract Method Interface)。

函数式接口在实际使用过程中很容易出错,比如某人在接口定义中又增加了另一个方法,则该接口不再是函数式接口,此时将该接口转换为Lambda表达式会报错。为了克服函数式接口的脆弱性,并且能够明确声明接口是作为函数式接口的意图,Java 8增加了**@FunctionalInterface**注解来标注函数式接口。

使用@FunctionalInterface注解标注的接口必须是函数式接口,也就是说该接口中只能声明一个抽象方法,如果声明多个抽象方法就会报错。但是默认方法和静态方法不属于抽象方法,因此在函数式接口中也可以定义默认方法和静态方法。

比如这样声明一个函数式接口是被允许的:

@FunctionalInterface
interface InterfaceDemo 
    void method(int a);
 	// 允许定义静态方法
    static void staticMethod() 
        ...
    
    // 允许定义默认方法
    default void defaultMethod() 
        ...
    

@FunctionalInterface注解不是必须的,如果一个接口符合"函数式接口"的定义,那么加不加该注解都没有影响,就像下面的接口。

interface InterfaceDemo2 
    void method(int a);

当然加上该注解能够更好地让编译器进行检查,也能提高代码的可读性。

4. Lambda表达式精简语法

  1. 参数类型可以省略

    比如I02 i02 = (int a) -> System.out.println(...);;可以写成I02 i02 = (a) -> System.out.println(...);;

  2. 假如只有一个参数,那么()括号可以省略

    比如I02 i02 = (a) -> System.out.println(...);;可以写成I02 i02 = a -> System.out.println(...);;

  3. 假如方法体只有一条语句,那么语句后的;分号和方法体的大括号可以一起省略

    比如I02 i02 = a -> System.out.println(...);;可以写成I02 i02 = a -> System.out.println(...);

  4. 如果方法体中唯一的语句是return返回语句,那么在省略第3种情况的同时,return也必须一起省略

    比如I05 i05 = a -> return 1;;可以写成``I05 i05 = a -> 1;`

5. 方法引用(Method Reference)

在Java 8中可以用方法引用来进一步简化Lambda表达式。(虽然两者在底层实现原理上略有不同,但在实际使用中完全可以视为等价)
有时候多个Lambda表达式的实现函数是一样的,我们可以封装成一个通用方法,再通过方法引用来实现接口

这话说的吧,就很难理解,我是这样理解的:因为是函数式接口嘛,所以接口里面就一个要实现的函数,然后我们可以通过lambda表达式来实现这个接口,进而实现其中的方法,也可以直接引用一个方法来作为这个方法的实现,然后这样就能实现这个接口对象了。

方法引用可以引用三种方法,他们的语法如下:

  • 如果是实例方法对象名::实例方法名
  • 如果是静态方法类名::实例方法名
  • 如果是构造方法类名::new

下面通过例子来理解一下方法引用的流程:

5.1 实例方法引用

示例代码:

public class Test 
    public void eat(int a) 
        System.out.println("吃东西。" + "a=" + a);
    
    public static void main(String[] args) 
        //Lambda表达式写法:
        Dog dog1 = (a) -> System.out.println("吃东西。" + "a=" + a);
        Cat cat1 = (a) -> System.out.println("吃东西。" + "a=" + a);
        dog1.doSomething(5);
        cat1.doSomething(5);
        //方法引用写法:
        Test test = new Test();
        Dog dog2 = test::eat;
        Cat cat2 = test::eat;
        dog2.doSomething(10);
        cat2.doSomething(10);
    

@FunctionalInterface
interface Dog 
    void doSomething(int a);

@FunctionalInterface
interface Cat 
    void doSomething(int a);

输出结果:

吃东西。a=5
吃东西。a=5
吃东西。a=10
吃东西。a=10

5.2 静态方法引用

示例代码:

package com.yyl.lambda;

public class Test 
    public static void eat(int a) 
        System.out.println("吃东西。" + "a=" + a);
    
    public static void main(String[] args) 
        //Lambda表达式写法:
        Dog dog1 = (a) -> System.out.println("吃东西。" + "a=" + a);
        Cat cat1 = (a) -> System.out.println("吃东西。" + "a=" + a);
        dog1.doSomething(5);
        cat1.doSomething(5);
        //方法引用写法:
        Test test = new Test();
        Dog dog2 = Test::eat;
        Cat cat2 = Test::eat;
        dog2.doSomething(10);
        cat2.doSomething(10);
    


@FunctionalInterface
interface Dog 
    void doSomething(int a);

@FunctionalInterface
interface Cat 
    void doSomething(int a);

5.3 构造方法引用

如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现(比如说接口方法与这个构造方法的参数个数、参数类型和返回值都对的上),那么就可以使用构造方法引用。

代码如下:

public class Test 
    public void eat(int a) 
        System.out.println("吃东西。" + "a=" + a);
    
    public static void main(String[] args) 
		//Lambda表达式写法:
        DogService dogService1 = (name, age) -> new Dog(name, age);
        System.out.println(dogService1.getDog("大黄", 5));
        //方法引用写法:
        DogService dogService2 = Dog::new;
        System.out.println(dogService2.getDog("二黑", 3));
    

@FunctionalInterface
interface DogService 
    Dog getDog(String name, int age);

class Dog 
    String name;
    int age;
    public Dog(String name, int age) 
        this.name = name;
        this.age = age;
    
    @Override
    public String toString() 
        return "Dog" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    

运行结果如下:

Dogname='大黄', age=5
Dogname='二黑', age=3

以上是关于Java8新特性:Lambda表达式函数式接口以及方法引用的主要内容,如果未能解决你的问题,请参考以下文章

Java8新特性——Lambda表达式之四大核心函数式接口 & 方法/构造器/数组引用

Java8新特性——Lambda表达式之四大核心函数式接口 & 方法/构造器/数组引用

java8新特性之Lambda表达式入门

Java8新特性——Lambda表达式之基本语法 & 自定义函数式接口

Java8新特性——Lambda表达式之基本语法 & 自定义函数式接口

Java8都有哪些新特性