热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

文章目录Java8新特性:Lambda表达式1.Lambda表达式概念2.Lambda表达式语法3.函数式接口(FunctionalInterface)4.Lambd

文章目录

  • Java8新特性:Lambda表达式
    • 1. Lambda表达式概念
    • 2. Lambda表达式语法
    • 3. 函数式接口(Functional Interface)
    • 4. Lambda表达式精简语法
    • 5. 方法引用(Method Reference)
        • 5.1 实例方法引用
        • 5.2 静态方法引用
        • 5.3 构造方法引用


Java8新特性:Lambda表达式

1. Lambda表达式概念

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

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

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

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

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

其基本语法如下:

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

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

整个的lambda表达式等价于:

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

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

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

结果是一样的:

image-20220915105845703

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

image-20220915130041118

看完以上代码已经能大概了解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;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +'}';}
}

运行结果如下:

Dog{name='大黄', age=5}
Dog{name='二黑', age=3}


推荐阅读
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
author-avatar
碎蜂CYM夜一
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有