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

news2024/11/16 15:47:30

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等;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物联网都有什么优缺点?——青创智通

工业物联网平台解决方案 物联网&#xff0c;这个曾经似乎遥不可及的科技概念&#xff0c;如今已逐渐渗透到我们生活的方方面面。从智能家居到智能工业&#xff0c;从智能医疗到智能城市&#xff0c;物联网技术正在改变着我们的世界。 然而&#xff0c;正如任何一项技术一样&a…

代码随想录算法训练营第五十四天||392.判断子序列、115.不同的子序列

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、392.判断子序列 思路 二、115.不同的子序列 思路 一、392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是…

FPGA中的乒乓操作

为什么不直接选用一个缓存更大的FIFO而选用乒乓操作为什么乒乓操作可以实现低速处理高速数据乒乓操作适用哪些场景 一、乒乓操作结构 首先先介绍一下乒乓操作的原理&#xff0c;其结构如下&#xff1a; 输入选择单元负责将数据送到数据缓冲模块&#xff0c;然后输出选择单元负…

51 html网页

上节内容的网页是hello world的字符串&#xff0c;但实际上网页应该是html格式的这种超文本标记语言&#xff0c;这一节完善一下网页的各种格式和内容 分文件 实际服务器中&#xff0c;网页的界面应该单独放一个文件&#xff0c;服务器从文件里读取网页的内容 先创建一个wroo…

有效运营企业内部社区的板块有哪些?

随着企业内部沟通和协作的重要性日益凸显&#xff0c;建立一个高效运营的企业内部社区成为越来越多企业的首要任务。针对不同的需求和目标&#xff0c;将企业内部社区分为多个板块&#xff0c;可以更好地促进员工之间的沟通、协作和共享知识。下面介绍如何从分多个板块创建的角…

Docker搭建Redis主从 + Redis哨兵模式(一主一从俩哨兵)

我这里是搭建一主一从&#xff0c;俩哨兵&#xff0c;准备两台服务器&#xff0c;分别安装docker 我这里有两台centos服务器 主服务器IP&#xff1a;192.168.252.134 从服务器IP&#xff1a;192.168.252.135 1.两台服务器分别拉取redis镜像 docker pull redis 2.查看镜像 d…

2024长三角快递物流展即将亮相,致鸿物流器材有限公司值得关注

广东致鸿物流器材有限公司&#xff0c;前身为广州致鸿物流器材有限公司&#xff0c;成立于2002年初&#xff0c;是一家中国专业仓储笼研发制造公司&#xff0c;公司员工约400名&#xff0c;日产仓储笼制造规模近8000个&#xff0c;在全国范围内有五大配送服务中心&#xff1a;江…

AI绘画Stable Diffusion XL 可商用模型!写实艺术时尚摄影级真实感大模型推荐(附模型下载)

大家好&#xff0c;我是设计师阿威 大家在使用AI绘画的时候&#xff0c;是不是遇到这种问题&#xff1a;收藏的模型确实很多&#xff0c;可商用的没几个&#xff0c;而今天阿威将给大家带来的这款写实艺术时尚摄影级真实感大模型-墨幽人造人XL&#xff0c; 对于个人来讲完全是…

应用弹窗优先级

背景 由于活动业务越来越多,积累的弹窗越来越多和杂乱,出现如下弹窗交互问题: 弹窗无限重叠,影响操作 弹出顺序无优先级,重要弹窗被隐藏 原因相信大家都一样,产品是一次次迭代的,也可能是不同人开发的,两个毫不相关的业务,弹窗时机也没有任何关联,重不重叠我怎么控制…

第8周 分布式事务与数据一致性主流解决方案落地

第8周 分布式事务与数据一致性主流解决方案落地 1. OpenFeign 服务间的远程调用_实战2. 客户端与服务端负载均衡机制_概念3. 微服务负载均衡NacosLoadbalancer_实战网关基于nacos实现负载均衡:默认轮询方式权重方式实现4. OSI七层网络模型_概念5. 微服务分布式环境下的事务问题…

Ubuntu22.04设置程序崩溃产生Core文件

Ubuntu22.04设置程序崩溃产生Core文件 文章目录 Ubuntu22.04设置程序崩溃产生Core文件摘要Ubuntu 生成Core文件配置1. 检查 core 文件大小限制2. 设置 core 文件大小限制3. 配置 core 文件命名和存储路径4. 重启系统或重新加载配置5. 测试配置 关键字&#xff1a; Ubuntu、 C…

VSCode连接远程服务器使用jupyter报错问题解决

目录 一. 问题描述二. jupyter环境确认三. 插件安装 一. 问题描述 经常会遇到一种问题就是, VSCode连接远程服务器, 上次jupyter notebook 还用的好好的, 下次打开就显示找不到内核了. 今天提供了全套解决方案, 帮大家迅速解决环境问题. 二. jupyter环境确认 首先进入自己需…

React 中的 Fiber 架构

React Fiber 介绍 React Fiber 是 React 的一种重写和改进的核心算法&#xff0c;用于实现更细粒度的更新和高效的调度。它是 React 16 版本中的一个重要更新&#xff0c;使得 React 能够更好地处理复杂和高频的用户交互。以下是对 React Fiber 的详细介绍&#xff1a; 为什么…

java中的工具类

以下是我们到现在学的三个类 在书写工具类的时候我们要遵循以下的规则 类名见面知意是为了知道这个工具类的作用 私有化构造方法的作用是为了不让外界不能创造这个类的对象吗&#xff0c;因为工具类不是描述一个事物的&#xff0c;它是一个工具。 方法定义位静态是为了方便调用…

8086 汇编笔记(一):寄存器

前言 8086 CPU 有 14 个寄存器&#xff0c;每个寄存器有一个名称。这些寄存器是&#xff1a;AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW 一、通用寄存器 8086 CPU 的所有寄存器都是 16 位的&#xff0c;可以存放两个字节。AX、BX、CX、DX 这 4个寄存器通常用…

剧本杀小程序开发,探索市场发展新的商业机遇

剧本杀游戏作为一个新兴行业&#xff0c;经历了爆发式的增长&#xff0c;剧本杀游戏在市场中的热度不断升高。 不过&#xff0c;在市场的火热下&#xff0c;竞争也在逐渐加大。因此&#xff0c;在市场竞争下&#xff0c;成本低、主题多样、有趣的线上剧本杀小程序成为了创业者…

永恒之蓝(MS17-010)详解

这个漏洞还蛮重要的&#xff0c;尤其在内网渗透和权限提升。 目录 SMB简介 SMB工作原理 永恒之蓝简原理 影响版本 漏洞复现 复现准备 复现过程 修复建议 SMB简介 SMB是一个协议服务器信息块&#xff0c;它是一种客户机/服务器、请求/响应协议&#xff0c;通过SMB协议…

两年半前端面字节,广度和深度让我不想做前端了

两年半经历&#xff0c;面的是前端工程师&#xff0c;字节面试官的问题挺有广度与深度的&#xff0c;这里整理一下面试过程中的题目&#xff08;有些忘记了&#xff09;&#xff0c;分享给大家: 面试过程中的问题 1、简单的自我介绍与项目经验。 2、一道算法题。 3、一道 …

揭秘!编写高质量代码的关键:码农必知的黄金法则!

文章目录 一、保持代码的简洁与清晰二、遵循良好的命名规范三、注重代码的可读性四、利用抽象与封装五、遵循SOLID原则六、关注代码性能七、确保代码安全性《码农修行&#xff1a;编写优雅代码的32条法则》编辑推荐内容简介目录前言/序言 在编程的世界里&#xff0c;每一位码农…

给转行产品经理的小白的一些建议

哈喽我是小源&#xff0c;毕业在教培大厂做了1年的班主任&#xff0c;下午1点上班&#xff0c;被优化后gap3月找到了自己的本命岗位——产品经理&#xff01; 其实这个转变也挺机缘巧合的&#xff0c;朋友和我都是教培行业&#xff0c;她是成人职教类&#xff0c;我是k111类&a…