一文搞懂Java8 Lambda表达式、方法引用

Lambda表达式介绍


Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。通过Lambda表达式,可以替代我们以前经常写的匿名内部类来实现接口。Lambda表达式本质是一个匿名函数;

体验Lambda表达式

我们通过一个小例子来体验下Lambda表达式,我们定义一个计算接口 只有一个方法add

public class Program {
 
    public static void main(String[] args) {
        Cal c1=new Cal() {
            @Override
            public int add(int a, int b) {
                return a+b;
            }
        };
       int c=c1.add(1,2);
        System.out.println(c);
    }
}
 
interface Cal{
     int add(int a,int b);
}


这个是我们以前的实现,匿名内部类,然后调用执行;我们现在用Lambda表达式改写下:

public class Program {
 
    public static void main(String[] args) {
        Cal c1=(int a,int b) ->{return a+b;};
        int c=c1.add(1,2);
        System.out.println(c);
    }
 
    int add(int a,int b){
        return a+b;
    }
}
 
interface Cal{
     int add(int a,int b);
}


匿名内部类,直接改成了:

Cal c1=(int a,int b) ->{return a+b;};

简洁多了,是不是感觉Lambda表达式挺强大,接下来我们来看看Lambda表达式的语法吧

Lambda表达式语法


我们看下这个Lambda表达式,这个本质是一个函数;

(int a,int b) ->{return a+b;};

一般的函数类似如下,有返回值,方法名,参数列表,方法体

int add(int a,int b){
  return a+b;
}

而Lambda表达式函数的话,只有参数列表,和方法体;

( 参数列表 ) -> { 方法体 }

( ) :用来描述参数列表;

{ } : 用来描述方法体;

->  :Lambda运算符,可以叫做箭头符号,或者goes to

注意:

1、lambda表达式要求接口是函数式接口。

2、lambda表达式引用的外部变量必须是事实最终变量。即初始化后值不会再改变。(不能在lambda改变外部变量的值。也不能在lambda内引用在外部会被改变的值,不管该值在外部的改变是在lambda表达式执行的前后)

3、在lambda表达式中声明 一个与方法的局部变量同名的参数 或 一个与方法的局部变量的同名的局部变量 是不合法的。

4、在一个方法中不能定义两个同名的局部变量,因此,lambda表达式中也同样不能有同名的局部变量。

5、在一个lambda表达式中使用this关键字,是指 创建这个lambda表达式的方法 所属的类 的this参数。

如下代码,这里调用的是Application对象的toString方法,而不是ActionListener接口的实例对象的方法。

public class Application{
    public void init(){
        ActionListener Listener = event -> {
            System.out.println(this.toString());    
        }
    }
}

Lambda表达式语法细讲


我们搞一个案例,接口方法(无参,单个参数,两个参数)X(有返回值,没有返回值)这六种情况都罗列下:

interface If1{
 
    /**
     * 无参数无返回值
     */
     void test();
}
 
 
interface If2{
 
    /**
     * 单个参数无返回值
     * @param a
     */
    void test(int a);
}
 
interface If3{
 
    /**
     * 两个参数无返回值
     * @param a
     * @param b
     */
    void test(int a,int b);
}
 
 
interface If4{
 
    /**
     * 无参数有返回值
     * @return
     */
    int test();
}
 
interface If5{
 
    /**
     * 单个参数有返回值
     * @param a
     * @return
     */
    int test(int a);
}
 
interface If6{
 
    /**
     * 多个参数有返回值
     * @param a
     * @param b
     * @return
     */
    int test(int a,int b);
}


我们用Lambda表达式实现:

// 无参数无返回值
If1 if1=()->{
  System.out.println("无参数无返回值");
};
if1.test();
 
// 单个参数无返回值
If2 if2=(int a)->{
  System.out.println("单个参数无返回值 a="+a);
};
if2.test(3);
 
// 两个参数无返回值
If3 if3=(int a,int b)->{
  System.out.println("两个参数无返回值 a+b="+(a+b));
};
if3.test(2,3);
 
// 无参数有返回值
If4 if4=()->{
  System.out.print("无参数有返回值 ");
  return 100;
};
System.out.println(if4.test());
 
 
// 单个参数有返回值
If5 if5=(int a)->{
  System.out.print("单个参数有返回值 ");
  return a;
};
System.out.println(if5.test(200));
 
// 多个参数有返回值
If6 if6=(int a,int b)->{
  System.out.print("多个参数有返回值 ");
  return a+b;
};
System.out.println(if6.test(1,2));


运行输出:

无参数无返回值
单个参数无返回值 a=3
两个参数无返回值 a+b=5
无参数有返回值 100
单个参数有返回值 200
多个参数有返回值 3


Lambda表达式精简语法


那语法注意点:

1、参数类型可以省略。

2、假如只有一个参数,()括号可以省略。

3、如果方法体只有一条语句,{}大括号可以省略。

4、如果方法体中唯一的语句是return返回语句,那省略大括号的同时return也要省略。

改写示例:

/**
 * @author java1234_小锋
 * @site www.java1234.com
 * @company Java知识分享网
 * @create 2020-08-12 16:43
 */
public class Program2 {
 
    public static void main(String[] args) {
        // 1,参数类型可以省略
        // 2,假如只有一个参数,()括号可以省略
        // 3,如果方法体只有一条语句,{}大括号可以省略
        // 4,如果方法体中唯一的语句是return返回语句,那省略大括号的同事return也要省略
 
        // 无参数无返回值
        If1 if1=()->System.out.println("无参数无返回值");
        if1.test();
 
        // 单个参数无返回值
        If2 if2=a->System.out.println("单个参数无返回值 a="+a);
        if2.test(3);
 
        // 两个参数无返回值
        If3 if3=(a,b)->{
            System.out.println("两个参数无返回值 a+b="+(a+b));
        };
        if3.test(2,3);
 
        // 无参数有返回值
        If4 if4=()->100;
        System.out.println(if4.test());
 
 
        // 单个参数有返回值
        If5 if5=a->{
            System.out.print("单个参数有返回值 ");
            return a;
        };
        System.out.println(if5.test(200));
 
        // 多个参数有返回值 参数类型可以省略
        If6 if6=(a,b)->a+b;
        System.out.println(if6.test(1,2));
 
    }
 
}


方法引用


有时候多个lambda表达式实现函数是一样的话,我们可以封装成通用方法,以便于维护(有错误时只需要修改一处,不需要把所有同样的lambda表达式都修改)。或者说,如果lambda表达式要表达的函数方案已经存在于某个方法的实现中,那么就可以通过双冒号来引用该方法作为lambda表达式的替代者。

这时候可以使用方法引用来达到目的:

语法是:对象::方法

假如是static方法,可以直接 类名::方法

注意:因为方法引用并不是把所引用的方法整体作为接口抽象方法的实现,而是把所引用的方法的方法体作为抽象方法的实现。因此,对所引用的方法的方法名、实例还是静态的都是不做要求的,只要返回值和参数列表与抽象方法相同,那么就可以替代lambda表达式作为抽象方法的实现。

(如果是接口的实现类那么重写方法时必须定义成实例的)

注意:接口必须是函数式接口!!(即只能含有一个抽象方法)(接口可以含多个静态方法)

另:方法参数列表可以是接口类型的引用,但要传入一个对该接口的实现。该方法可以是方法引用,也可以是lambda表达式。

示例如下:

package B;
 
public class Program2{
    public static void main(String[] args) {
        // 使用lambda表达式的方法实现接口
        If5 i0 = (i)->{
            return i;
        };
        i0.test(0);
 
        // 实例方法 对象名::方法名
        Program2 p = new Program2();
        If5 i1 = p::A;
        System.out.println(i1.test(5));
 
        // 静态方法 类名::方法名
        If6 i2 = Program2::B;
        System.out.println(i2.test2(6));
        
        ling.KK(999,i1);    
        // 另,方法参数列表可以是接口类型的引用,但要传一个对该接口的实现
        // 该实现可以是方法引用,也可以是lambda表达式
 
    }
 
    public int A(int i) {   // 对名字不做要求、对静态的实例的也不做要求
        return i;
    }
 
    public static int B(int i) {    // 对名字不做要求、对静态的实例的也不做要求
        return i;
    }
    
}
 
 
 
interface If5{
    int test(int i);    // 必须是函数式接口
}
 
interface If6{
    int test2(int i);    // 必须是函数式接口
}
 
class ling{
    public static void KK(int number,If5 ii){
        ii.test(number);
    }
}


 
如果想调用的方法所在的类是父类的方法,则可以使用super::方法名,

如果想调用的方法所在的类是父类的方法,则可以使用this::方法名。

示例如下:

interface If5{
    int test(int i);    // 必须是函数式接口
}
 
class father{
    public int A(int i) {
        return i;
    }
}
 
class son extends father {
    public void KK() {
        If5 i1 = super::A;
        System.out.println(i1.test(1));
 
        If5 i2 = this::B;
        System.out.println(i2.test(2));
    }
 
    public int B(int i) {
        return i;
    }
}


构造方法引用


如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用;

语法:类名::new

示例如下:

public class Dog {
 
    private String name;
 
    private int age;
 
    public Dog() {
        System.out.println("无参构造方法");
    }
 
    public Dog(String name, int age) {
        System.out.println("有参构造方法");
        this.name = name;
        this.age = age;
    }
 
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
 
interface DogService{
    Dog getDog();
 
}
 
interface DogService2{
    Dog getDog(String name,int age);
}
 
public class Program3 {
 
    public static void main(String[] args) {
 
        // 普通方式
        DogService dogService=()->{
            return new Dog();
        };
        dogService.getDog();
 
        // 简化方式
        DogService dogService2=()->new Dog();
        dogService2.getDog();
 
        // 构造方法引用
        DogService dogService3=Dog::new;
        dogService3.getDog();
 
        // 构造方法引用 有参
        DogService2 dogService4=Dog::new;
        dogService4.getDog("小米",11);
 
        // 错误方式:
        DogService dogService5=()->Dog::new;
        dogService5.getDog();
        // 相当于以上的简化方法,但这样是return Dog::new ,这不是对接口的实现,接口的抽象方法是返回Dog类型,不是DogService类型的Dog::new
    }
}


执行结果:

无参构造方法
无参构造方法
无参构造方法
有参构造方法


综合示例:

下面我们通过一个lambda操作集合的综合示例,来深入体验下Lambda表达式用法;

public class Program4 {
 
    public static void main(String[] args) {
        List<Dog> list=new ArrayList<>();
        list.add(new Dog("aa",1));
        list.add(new Dog("bb",4));
        list.add(new Dog("cc",3));
        list.add(new Dog("dd",2));
        list.add(new Dog("ee",5));
        // 排序
        System.out.println("lambda集合排序");
        list.sort((o1,o2)->o1.getAge()-o2.getAge());
        // 需要一个实现Comparator接口的类对象,可用lambda表达式代替。
        
        // 遍历集合
        System.out.println("lambda遍历集合");
        list.forEach(System.out::println);
        // 每读取一个元素,就调用System.out类的println方法
    }
}


运行输出:

lambda集合排序
[Dog{name='aa', age=1}, Dog{name='dd', age=2}, Dog{name='cc', age=3}, Dog{name='bb', age=4}, Dog{name='ee', age=5}]
lambda遍历集合
Dog{name='aa', age=1}
Dog{name='dd', age=2}
Dog{name='cc', age=3}
Dog{name='bb', age=4}
Dog{name='ee', age=5}


数组的构造器引用:


如果接口的抽象方法是接收一个数组长度然后返回一个数组,那么可以使用数组的构造器引用来实现该抽象方法。

示例如下:

// 定义一个函数式接口
@FunctionalInterface
interface BuildArrays{
    double[] buildArrays(int length);
}
 
public class Demo{
    public static double[] buildArrays(int length,BuildArrays buildArrays) {
        return buildArrays.buildArrays(length);
        // 执行到此处时接口已被实现为double类型数组的创建,调用时传入所需要创建的长度即可。
    }
 
    public static void main(String[] args) {
        // 调用本类的buildArrays方法,该方法又通过接口的引用调用接口的buildArrays方法
        double[] arr01 = buildArrays(10, length -> new double[length]);
        System.out.println(arr01.length); // 10
 
        double[] arr02 = buildArrays(10, double[] :: new);
        System.out.println(arr02.length); // 10
 
    }
}


@FunctionalInterface注解


前面我们会发现Consumer接口,Comparator接口都有

@FunctionalInterface注解;

这个注解是函数式接口注解,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。lambda表达式要求是函数式接口,方法引用也要求是函数式接口。

这种类型的接口也称为SAM接口,即Single Abstract Method interfaces

特点

接口有且仅有一个抽象方法

允许定义静态方法

允许定义默认方法

允许java.lang.Object中的public方法

该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错

示例如下:

// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {
 
    void add();
    
    void sub();
}


系统内置函数式接口

Java8的推出,是以Lambda重要特性,一起推出的,其中系统内置了一系列函数式接口;

在jdk的java.util.function包下,有一系列的内置函数式接口:

比如常用的Consumer,Comparator,Predicate,Supplier等;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/659095.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单元测试框架Pytest的基本操作

Pytest基本操作 1. 详解1.1 命名规则:1.2 自定义查找规则:1.3 3种运行方式1.4 执行顺序2. 断言2.1 定义2.2 断言的规则3. mark3.1 mark的作用3.2 mark的标记方式3.3 注册标签名3.4 skip跳过标记4. pytest的参数化5. pytest的夹具(fixture测试夹具)5.1. 作用5.2. 夹具应用场…

Java网络编程:UDP通信篇

目录 UDP协议 Java中的UDP通信 DatagramSocket DatagramPacket UDP客户端-服务端代码实现 UDP协议 对于UDP协议&#xff0c;这里简单做一下介绍&#xff1a; 在TCP/IP协议簇中&#xff0c;用户数据报协议&#xff08;UDP&#xff09;是传输层的一个主要协议之一&#xf…

LeetCode hot100-57-G

17. 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。不会&#xff0c;放IDEA里执行了一下大概理解了流程 …

《Ai企业知识库》-rasa-初步使用

根据上面的环境准备之后&#xff1a; 《Ai企业知识库》-模型实践-rasa开源学习框架-搭建简易机器人-环境准备(针对windows)-02-CSDN博客 基础的使用&#xff1a; rasa项目初始化&#xff1a; rasa init 首先进入目标文件夹 在dos窗口&#xff08;目标文件夹下&#xff09…

10Django项目--用户管理系统--改

对应视频链接点击直达 10Django项目--用户管理系统--改 对应视频链接点击直达改a&#xff0c;本质b&#xff0c;修改(更新) 页面相关a&#xff0c;index页面新增操作按钮b&#xff0c;修改(更新)页面c&#xff0c;路由和函数 OVER&#xff0c;不会有人不会吧不会的加Q139400651…

pdf只要其中一页 pdf只要第一页怎么办 pdf只要前几页怎么弄

在现代办公环境中&#xff0c;PDF文件已经成为我们日常工作中不可或缺的一部分。然而&#xff0c;有时我们可能只需要PDF文件中的某一页&#xff0c;而不是整个文件。这时&#xff0c;我们该如何操作才能只获取所需的那一页呢&#xff1f;本文将详细操作方法&#xff0c;帮助大…

全面盘点多模态融合算法及应用场景

关注作者&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕博&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理专业人士&#xff0c;上亿营收AI产品研发负责人 多…

工控一体机10.1寸显示器电容触摸屏(YA1308101JK)产品规格说明书

如果您对工控一体机有任何疑问或需求&#xff0c;或者对如何集成工控一体机到您的业务感兴趣&#xff0c;可移步控芯捷科技。 一、硬件功能介绍 YA1308101JK产品介绍&#xff1a; YA1308101JK搭载 Android10 主流操作系统&#xff0c;具有系统版本更高、占用内存更低、运行效率…

LeetCode583:两个字符串的删除操作

题目描述 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 代码 解法1 /*dp[i][j]&#xff1a;以i-1为结尾的wrod1中有以j-1为尾的word2的个数为了让word1和word2相同&#xff0c;最少操作…

什么是老板和工程师都喜欢的FMEA?——FMEA软件

免费试用FMEA软件-免费版-SunFMEA 在企业管理与工程技术领域&#xff0c;FMEA&#xff08;潜在失效模式与效应分析&#xff09;早已不仅仅是一个概念或工具&#xff0c;它更是一种思维方式和团队协作的精髓。那么&#xff0c;究竟什么才是老板和工程师都喜欢的FMEA呢&#xff…

学习笔记——动态路由协议——OSPF(OSPF网络类型2)

2、OSPF网络类型 常见链路层协议对应的默认网络类型 网络类型 描述 常见链路层协议 Hello报文间隔 报文类型 有无DR、BDR选举 P2P 点对点网络 HDLC、PPP、LAPB 10s 以组播方式发送OSPF报文 有 P2MP 点对多点网络 无 30s 以组播方式发送Hello报文&#xff0c;单…

YOLOv10最详细全面讲解2- 目标检测-环境搭建、训练自己的数据集

YOLOv10没想到出来的如此之快&#xff0c;作为一名YOLO的爱好者&#xff0c;以YOLOv5和YOLOv8的经验&#xff0c;打算出一套从数据集装备->环境配置->训练->验证->目标追踪全系列教程。请大家多多点赞和收藏&#xff01;&#xff01;&#xff01; 系列文章&#xf…

基于h5和大数据的游戏数据型网站-计算机毕业设计源码30844

摘 要 在目前的形势下&#xff0c;科技力量已成为我国的主要竞争力。而在科学技术领域&#xff0c;计算机的使用逐渐达到成熟&#xff0c;无论是从国家到企业再到家庭&#xff0c;计算机都发挥着其不可替代的作用&#xff0c;可以说计算机的可用领域遍及生活、工作的各个方面。…

TPM是如何平衡设备维护与生产需求的?

在当今快节奏的生产环境中&#xff0c;设备维护与生产需求之间的平衡成为了企业持续发展的关键所在。TPM&#xff08;全面生产维护&#xff09;作为一种先进的生产管理理念&#xff0c;为企业提供了实现这一平衡的有效路径。具体如深圳天行健精益管理咨询公司下文所述&#xff…

四川古力未来科技抖音小店畅享多重好处

在当今数字化浪潮席卷之下&#xff0c;四川古力未来科技抖音小店以其独特的魅力&#xff0c;正逐渐成为消费者们的新宠。作为融合了先进科技与便捷购物体验的创新平台&#xff0c;它不仅能够满足消费者的多样化需求&#xff0c;更在提升购物体验、优化服务流程等方面展现出了显…

【ai】livekit服务本地开发模式及example app信令交互详细流程

文档要安装git lfs 下载当前最新版本1.6.1 windows版本&#xff1a;启动dev模式 服务器启动 (.venv) PS D:\XTRANS\pythonProject\LIVEKIT> cd .\livekit_release\ (.venv) PS D:\XTRANS\pythonProject\LIVEKIT\livekit_release> lsDirectory: D:\XTRANS\pythonProject\L…

MyBatis入门——MyBatis的基础操作(2)

目录 一、打印日志 二、参数传递 常见错误&#xff1a;使用对象接受 小结&#xff1a; 三、增&#xff08;Insert&#xff09; 返回主键 四、删&#xff08;Delete&#xff09; 五、改&#xff08;Update&#xff09; 六、查&#xff08;Select&#xff09; 1、起别名…

【wiki知识库】03.前后端的初步交互(展现所有的电子书)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;今日目标 二、&#x1f4c2;前端配置文件补充 三、&#x1f30f;前端Vue的改造 四、&#x1f4a1;总结 一、&#x1f525;今日目标 在上一篇文章当中&#xff0c;我已带大家把后端的一些基本工…

最新微信发卡小程序 发卡网卡密系统流支持量主

2024.4更新 1.修复分类介绍报错 2.修改前端UI 3.增加插屏弹出广告 4.禁止PC端使用(PC端小程序没有广告) 免费下载&#xff1a;发卡小程序 卡密系统流支持量主 多种卡密领取模式【亲测】_麦田吧 前端演示地址&#xff1a;扫码查看 源码功能&#xff1a; 小程序系统/多种卡密领…

vscode远程登录阿里云服务器【使用密钥方式--后期无需再进行密码登录】【外包需要密码】

1&#xff1a;windows主机上生成【私钥】【公钥】 1.1生成公钥时不设置额外密码 1.2生成公钥时设置额外密码【给外包人员使用的方法】 2&#xff1a;在linux服务器中添加【公钥】 3&#xff1a;本地vscode连接linux服务器的配置 操作流程如下 1.1本地终端中【生成免密登录…