首页 科技正文

欧博代理:Java 基础 —— Lambda 表达式

admin 科技 2020-08-07 61 1

概述

阅读项目代码时,尤其是阅读一些源码时,经常会遇到 Lambda 表达式。对此之前看过相关文章,然则停留在模模糊糊的印象上。今天趁着有时间,通过一些 demo 示例,梳理一下它的用法,以备后期遗忘的时刻快速查询它的用法!

Lambda 表达式是 Java 8 的主要更新,它支持将代码块作为方式参数、允许使用更简练的代码来建立只有一个抽象方式的接口的实例。

形貌中提到的接口称为函数式接口

语法

Lambda 表达式的主要作用就是可以用于简化建立匿名内部类工具,Lambda 表达式的代码块将会用于实现抽象方式的方式体,Lambda 表达式就相当于一个匿名方式。

Lambda 表达式由三部门组成:

  • 形参列表:形参列表允许省略类型,若是形参列表中只有一个参数,形参列表的圆括号也可以省略;
  • 箭头(->):通过英文画线和大于符号组成;
  • 代码块:若是代码块只有一条语句,花括号可以省略。Lambda 代码块只有一条 return 语句,可以省略 return 关键字,Lambda 表达式会自动返回这条语句的值作为返回值。

示例

interface Eatable {
    void taste();
}

interface Flyable {
    void fly(String weather);
}

interface Addable {
    int add(int a, int b);
}


public class LambdaQs {
    // 挪用该方式需要传入一个 Eatable 类型的工具
    public void eat(Eatable e) {
        System.out.println(e);
        e.taste();
    }

    // 挪用该方式需要传入 Flyable 类型的工具
    public void drive(Flyable f) {
        System.out.println("我正在驾驶:" + f);
        f.fly("「夏日晴天」");
    }

    // 挪用该方式需要 Addable 类型的工具
    public void calc(Addable add) {
        System.out.println("5 + 3 = " + add.add(5, 3));
    }

    public static void main(String[] args) {
        LambdaQs lq = new LambdaQs();
        // Lambda 表达式的代码块只有一句,因此省略了花括号
        lq.eat(() -> System.out.println("雪糕的味道不错!"));
        // Lambda 表达式的形参只有一个参数,因此省略了圆括号
        lq.drive(weather -> {
            // 对接口中抽象方式 fly 的重写
            System.out.println("今天天气是:" + weather);
            System.out.println("飞机平稳航行!");
        });
        // Lambda 表达式只有一条语句,纵然该表达式需要返回值,也可以省略 return
        lq.calc((a, b) -> a + b);
        // 若是不用 Lambda 表达式,就需要如下匿名类的方式去重写抽象方式
        lq.calc(new Addable() {
            @Override
            public int add(int a, int b) {
                return a + b;
            }
        });
    }
}

输出效果:

oop.lambda.LambdaQs$$Lambda$1/1607521710@7ef20235
雪糕的味道不错!
我正在驾驶:oop.lambda.LambdaQs$$Lambda$2/1329552164@15aeb7ab
今天天气是:「夏日晴天」
飞机平稳航行!
5 + 3 = 8
5 + 3 = 8

以上示例可以说明,Lambda 表达式现实上可以被当做一个详细的工具。

Lambda 表达式与函数式接口

Lambda 表达式的类型,也被称为「目的类型(target type)」。Lambda 表达式的目的类型必须是「函数式接口(functional interface)」。函数式接口代表只包罗一个抽象方式的接口。函数式接口可以包罗多个默认方式、类方式,但仅能声明一个抽象方式。

查询 Java 8 的 API 文档,可以发现大量的函数式接口,例如:Runnable、ActionListener 等接口都是函数式接口。

Java 8 专门为函数式接口提供了 @FunctionalInterface 注解。该注解就是用于告诉编译器校验接口必须是函数式接口,否则就报错。

由于 Lambda 表达式的效果就是被当做工具/实例,因此,可以使用 Lambda 表达式举行赋值,示例:

Runnable r = () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

我们看一下 Runnable 接口的界说:

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

看一个错误示例:

Object obj = () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

上面这段代码会报错:Target type of a lambda conversion must be an interface。Lambda 表达式的目的类型必须是明确的函数式接口!将 Lambda 表达式赋值给 Object 类型的变量,编译器只能推断出它的表达类型为 Object,而 Object 并不是函数式接口,因此就报错了!

为了保证 Lambda 表达式的目的类型是明确的函数式接口,有如下三种常见方式:

  • 将 Lambda 表达式赋值给函数式接口类型的变量;
  • 将 Lambda 表达式作为函数式接口类型的参数传给某个方式;
  • 使用函数式接口对 Lambda 表达式举行强制类型转换;

将上面失足的代码可以举行如下的改写:

Object obj1 = (Runnable)() -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

综上,Lambda 表达式的本质很简朴,就是使用简朴的语法来建立函数式接口的实例,制止匿名内部类的繁琐。

方式引用于组织器引用

若是 Lambda 表达式的代码块只有一条代码,还可以在代码中使用方式引用和组织器引用。

方式引用和组织器引用的利益是使 Lambda 表达式的代码块加倍简练。方式引用和组织器引用都需要使用两个英文冒号 ::

种类 示例 说明 对应的 Lambda 表达式
引用类方式 类名::类方式 函数式接口中被实现的方式的所有参数传给该类方式作为参数 (a,b,...) -> 类名.类方式(a,b,...)
引用特定工具的实例方式 特定工具::实例方式 函数式接口中被实现的方式的所有参数传给该方式作为参数 (a,b,...) -> 特定工具.实例方式(a,b,...)
引用某类工具的实例方式 类名::实例方式 函数式接口中被实现的方式的第一个参数作为挪用者,后面的参数所有传给该方式作为参数 (a,b,...)->a.实例方式(b,...)
引用组织器 类名::new 函数式接口中被实现方式的所有参数传给该组织器作为参数 (a,b,...)->new 类名(a,b,...)
@FunctionalInterface
interface Converter {
    Integer convert(String from);
}

@FunctionalInterface
interface MyTest {
    String test(String a, int b, int c);
}

@FunctionalInterface
interface YourTest {
    // 抽象方式卖力凭据 String 参数天生一个 JFrame 返回值
    JFrame win(String title);
}


public class LambdaRef {
    public static void main(String[] args) {
        // 1 引用类方式
        // 下面使用 Lambda 表达式建立 Converter 工具
        Converter converter1 = from -> Integer.valueOf(from);
        Integer val = converter1.convert("99");

        // 函数式接口中被实现方式的所有参数传给该类方式作为参数
        Converter converter2 = Integer::valueOf;
        Integer val2 = converter2.convert("100");

        // 2 引用特定工具的实例方式
        // 使用 Lmabda 表达式建立 Converter 工具
        Converter converter3 = from -> "hello michael翔".indexOf(from);

        // 挪用 "hello michael翔"的indexOf()实例方式
        // 函数式接口中被实现的所有参数传给该方式作为参数
        Converter converter4 = "hello michael翔"::indexOf;

        // 3 引用某类工具的实例方式
        // 使用 Lambda 表达式建立 MyTest 工具
        MyTest mt = (a, b, c) -> a.substring(b, c);
        String  str = mt.test("Hello World, Hello Michael翔", 2,9);

        // 上面 Lambda 表达式只有一行,因此可以使用如下引用举行替换
        // 函数式接口中被实现方式的第一个参数作为挪用者
        // 后面的参数所有传给该方式作为参数
        MyTest str2 = String::substring;

        // 4 引用组织器
        // 使用 Lambda 表达式建立 YourTest 工具
        YourTest yt = a -> new JFrame(a);
        JFrame jf = yt.win("窗口");

        // 使用组织器引用举行替换
        // 函数式接口中被实现方式的所有参数传给该组织器作为参数
        YourTest yt2 = JFrame::new;
        JFrame jf2 = yt.win("窗口2");
    }
}

Lambda 表达式与匿名内部类的联系与区别

Lambda 表达式与匿名内部类存在如下相同点:

  • Lambda 表达式与匿名内部类一样,都可以直接接见 effectively final 的局部变量,以及外部类的成员变量(包罗示例变量和类变量);
  • Lambda 表达式建立的工具与匿名内部类天生的工具一样,都可以直接挪用从接口中继续的默认方式;

Lambda 表达式与匿名内部类的区别:

  • 匿名内部类可以为随便接口建立实例,不管接口包罗多少个抽象方式,只要匿名内部类实现所有抽象方式即可;然则 Lambda 表达式只能为函数式接口建立实例;
  • 匿名内部类可以为抽象类甚至通俗类建立实例,然则 Lambda 表达式只能为函数式接口建立实例;
  • 匿名内部类实现的抽象方式体允许挪用接口中界说的默认方式,然则 Lambda 表达式的代码块不允许挪用接口中界说的默认方式;
@FunctionalInterface
interface Converter {
    Integer convert(String from);
}

@FunctionalInterface
interface MyTest {
    String test(String a, int b, int c);
}

@FunctionalInterface
interface YourTest {
    // 抽象方式卖力凭据 String 参数天生一个 JFrame 返回值
    JFrame win(String title);
}


public class LambdaRef {
    public static void main(String[] args) {
        // 1 引用类方式
        // 下面使用 Lambda 表达式建立 Converter 工具
        Converter converter1 = from -> Integer.valueOf(from);
        Integer val = converter1.convert("99");

        // 函数式接口中被实现方式的所有参数传给该类方式作为参数
        Converter converter2 = Integer::valueOf;
        Integer val2 = converter2.convert("100");

        // 2 引用特定工具的实例方式
        // 使用 Lmabda 表达式建立 Converter 工具
        Converter converter3 = from -> "hello michael翔".indexOf(from);

        // 挪用 "hello michael翔"的indexOf()实例方式
        // 函数式接口中被实现的所有参数传给该方式作为参数
        Converter converter4 = "hello michael翔"::indexOf;

        // 3 引用某类工具的实例方式
        // 使用 Lambda 表达式建立 MyTest 工具
        MyTest mt = (a, b, c) -> a.substring(b, c);
        String  str = mt.test("Hello World, Hello Michael翔", 2,9);

        // 上面 Lambda 表达式只有一行,因此可以使用如下引用举行替换
        // 函数式接口中被实现方式的第一个参数作为挪用者
        // 后面的参数所有传给该方式作为参数
        MyTest str2 = String::substring;

        // 4 引用组织器
        // 使用 Lambda 表达式建立 YourTest 工具
        YourTest yt = a -> new JFrame(a);
        JFrame jf = yt.win("窗口");

        // 使用组织器引用举行替换
        // 函数式接口中被实现方式的所有参数传给该组织器作为参数
        YourTest yt2 = JFrame::new;
        JFrame jf2 = yt.win("窗口2");
    }
}

Lambda 表达式挪用 Arrays 的类方式

Arrays 类的有些方式需要 Comparator、XxxOperator、XxxFunction 等接口的实例,这些接口都是函数式接口。因此,可以使用 Lambda 表达式来挪用 Arrays 的方式。

public class LambdaArrays {
    public static void main(String[] args) {
        String[] arr1 = new String[]{"java", "python", "rust", "go"};
        Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
        System.out.println(Arrays.toString(arr1));
        int[] arr2 = {3, -4, 25, 16, 30, 18};
        // left 代表数组中前一个索引处的元素,盘算第一个元素时,left 为 1;
        // right 代表数组中的当前索引处的元素
        Arrays.parallelPrefix(arr2, (left, right) -> left * right);
        System.out.println(Arrays.toString(arr2));
        long[] arr3 = new long[5];
        // a 代表正在盘算的元素索引
        Arrays.parallelSetAll(arr3, a -> a * 5);
        System.out.println(Arrays.toString(arr3));
        
        // 等价于用匿名内部类重写 applyAsLong 抽象方式
        Arrays.parallelSetAll(arr3, new IntToLongFunction() {
            @Override
            public long applyAsLong(int value) {
                return value * 5;
            }
        });
        System.out.println(Arrays.toString(arr3));
    }
}

输出:

[go, java, rust, python]
[3, -12, -300, -4800, -144000, -2592000]
[0, 5, 10, 15, 20]
[0, 5, 10, 15, 20]

由于这些要收支 Comparator、XxxOperator、XxxFunction 等接口的实例往往都是一次性的,使用 Lambda 表达式也不用思量重用等,反而让程序加倍简练了。

总结

本文主要参考的是 《疯狂 Java 课本第 5 版》的第 6 章的面向工具下,通过现实的示例 demo 应该可以将 Lambda 的常用场景和用法掌握了。这样,看项目代码或者源码的话,会加倍易于明白!基本功扎实,才气走得更快!

参考

  • To Be Top Javaer/糖块十二、Lambda表达式

,

欧博手机版

欢迎进入欧博手机版(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

版权声明

本文仅代表作者观点,
不代表本站Allbet的立场。
本文系作者授权发表,未经许可,不得转载。

评论

精彩评论
  • 2020-08-07 00:01:22

    欧博开户网址欢迎进入欧博开户网址(Allbet Gaming):www.aLLbetgame.us,欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。闲下来看