搜索
首页Javajava教程Java Lambda 表达式详解及示例代码

Java Lambda 表达式详解及示例代码

Jan 23, 2017 pm 01:24 PM
java lambda 表达式

Java Lambda 表达式是 Java 8 引入的一个新的功能,可以说是模拟函数式编程的一个语法糖,类似于 Javascript 中的闭包,但又有些不同,主要目的是提供一个函数化的语法来简化我们的编码。

Lambda 基本语法

Lambda 的基本结构为 (arguments) -> body,有如下几种情况:

参数类型可推导时,不需要指定类型,如 (a) -> System.out.println(a)

当只有一个参数且类型可推导时,不强制写 (), 如 a -> System.out.println(a)

参数指定类型时,必须有括号,如 (int a) -> System.out.println(a)

参数可以为空,如 () -> System.out.println(“hello”)

body 需要用 {} 包含语句,当只有一条语句时 {} 可省略

常见的写法如下:

(a) -> a * a
(int a, int b) -> a + b
(a, b) -> {return a - b;}
() -> System.out.println(Thread.currentThread().getId())

函数式接口 FunctionalInterface

概念

Java Lambda 表达式以函数式接口为基础。什么是函数式接口(FunctionalInterface)? 简单说来就是只有一个方法(函数)的接口,这类接口的目的是为了一个单一的操作,也就相当于一个单一的函数了。常见的接口如:Runnable, Comparator 都是函数式接口,并且都标注了注解 @FunctionalInterface 。

举例

以 Thread 为例说明很容易理解。Runnable 接口是我们线程编程时常用的一个接口,就包含一个方法 void run(),这个方法就是线程的运行逻辑。按照以前的语法,我们新建线程一般要用到 Runnable 的匿名类,如下:

new Thread(new Runnable() {
  @Override
  public void run() {
    System.out.println(Thread.currentThread().getId());
  }
 
}).start();

如果写多了,是不是很无聊,而基于 Lambda 的写法则变得简洁明了,如下:

new Thread(() -> System.out.println(Thread.currentThread().getId())).start();

注意 Thread 的参数,Runnable 的匿名实现就通过一句就实现了出来,写成下面的更好理解

Runnable r = () -> System.out.println(Thread.currentThread().getId());
new Thread(r).start();

当然 Lambda 的目的不仅仅是写起来简洁,更高层次的目的等体会到了再总结。

再看一个比较器的例子,按照传统的写法,如下:

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, new Comparator<Integer>() {
  @Override
  public int compare(Integer o1, Integer o2) {
    return o1 - o2;
  }
});

Lambda 表达式写法如下:

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, (o1, o2) -> o1 - o2);

JDK中的函数式接口

为了现有的类库能够直接使用 Lambda 表达式,Java 8 以前存在一些接口已经被标注为函数式接口的:

java.lang.Runnable

java.util.Comparator

java.util.concurrent.Callable

java.io.FileFilter

java.security.PrivilegedAction

java.beans.PropertyChangeListener

Java 8 中更是新增加了一个包 java.util.function,带来了常用的函数式接口:

Function469df1b70b2914a3841e0404ec3e3d5d - 函数:输入 T 输出 R

BiFunction96b8c9ba1c8b330d622a8468a6113c2e - 函数:输入 T 和 U 输出 R 对象

Predicate8742468051c85b06f0a0af9e3e506b5c - 断言/判断:输入 T 输出 boolean

BiPredicateabbd655bd3f9f929be0207abcc18a2ef - 断言/判断:输入 T 和 U 输出 boolean

Supplier8742468051c85b06f0a0af9e3e506b5c - 生产者:无输入,输出 T

Consumer8742468051c85b06f0a0af9e3e506b5c - 消费者:输入 T,无输出

BiConsumerabbd655bd3f9f929be0207abcc18a2ef - 消费者:输入 T 和 U 无输出

UnaryOperator8742468051c85b06f0a0af9e3e506b5c - 单元运算:输入 T 输出 T

BinaryOperator8742468051c85b06f0a0af9e3e506b5c - 二元运算:输入 T 和 T 输出 T

另外还对基本类型的处理增加了更加具体的函数是接口,包括:BooleanSupplier, DoubleBinaryOperator, DoubleConsumer, DoubleFunction0f1763a9dfcc95d54eac98034f0cdcdd, DoublePredicate, DoubleSupplier, DoubleToIntFunction, DoubleToLongFunction, DoubleUnaryOperator, IntBinaryOperator, IntConsumer, IntFunction0f1763a9dfcc95d54eac98034f0cdcdd, IntPredicate, IntSupplier, IntToDoubleFunction, IntToLongFunction, IntUnaryOperator, LongBinaryOperator, LongConsumer,LongFunction0f1763a9dfcc95d54eac98034f0cdcdd, LongPredicate, LongSupplier, LongToDoubleFunction,LongToIntFunction, LongUnaryOperator, ToDoubleBiFunctionabbd655bd3f9f929be0207abcc18a2ef, ToDoubleFunction8742468051c85b06f0a0af9e3e506b5c,ToIntBiFunctionabbd655bd3f9f929be0207abcc18a2ef, ToIntFunction8742468051c85b06f0a0af9e3e506b5c, ToLongBiFunctionabbd655bd3f9f929be0207abcc18a2ef, ToLongFunction8742468051c85b06f0a0af9e3e506b5c 。结合上面的函数式接口,对这些基本类型的函数式接口通过类名就能一眼看出接口的作用。

创建函数式接口

有时候我们需要自己实现一个函数式接口,做法也很简单,首先你要保证此接口只能有一个函数操作,然后在接口类型上标注注解 @FunctionalInterface 即可。

类型推导

类型推导是 Lambda 表达式的基础,类型推导的过程就是 Lambda 表达式的编译过程。以下面的代码为例:

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);
编译期间,我理解的类型推导的过程如下:

先确定目标类型 Function

Function 作为函数式接口,其方法签名为:Integer apply(String t)

检测 str -> Integer.parseInt(str) 是否与方法签名匹配(方法的参数类型、个数、顺序 和返回值类型)

如果不匹配,则报编译错误

这里的目标类型是关键,通过目标类型获取方法签名,然后和 Lambda 表达式做出对比。

方法引用

方法引用(Method Reference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与 Lambda 表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的 Lambda 表达式的简化。

方法引用的语法为: Type::methodName 或者 instanceName::methodName , 构造函数对应的 methodName 为 new。

例如上面曾用到例子:

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

对应的方法引用的写法为

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等

构造方法引用

语法为: Type::new 。 如下面的函数为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::new;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> new Integer(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return new Integer(str);
  }
};

数组构造方法引用

语法为: Type[]::new 。如下面的函数为了构造一个指定长度的字符串数组

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = String[]::new;

方法引用写法

Function440e1640c9faa3393c37ba0de3f32bfa fixedArray = length -> new String[length];

传统写法

Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() {
  @Override
  public String[] apply(Integer length) {
    return new String[length];
  }
};

静态方法引用

语法为: Type::new 。 如下面的函数同样为了将字符串转为数组

方法引用写法

Function00c20620d278363633dd30e58ef30cbd strToInt = Integer::parseInt;

Lambda 写法

Function00c20620d278363633dd30e58ef30cbd strToInt = str -> Integer.parseInt(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
  @Override
  public Integer apply(String str) {
    return Integer.parseInt(str);
  }
};

实例上实例方法引用

语法为: instanceName::methodName 。如下面的判断函数用来判断给定的姓名是否在列表中存在

Listf7e83be87db5cd2d9a8a0b8117b38cd4 names = Arrays.asList(new String[]{"张三", "李四", "王五"});
Predicatef7e83be87db5cd2d9a8a0b8117b38cd4 checkNameExists = names::contains;
System.out.println(checkNameExists.test("张三"));
System.out.println(checkNameExists.test("张四"));

类型上实例方法引用

语法为: Type::methodName 。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度

Function<String, Integer> calcStrLength = String::length;
System.out.println(calcStrLength.apply("张三"));
List<String> names = Arrays.asList(new String[]{"zhangsan", "lisi", "wangwu"});
names.stream().map(String::length).forEach(System.out::println);<br>

   

又比如下面的函数已指定的分隔符分割字符串为数组

BiFunction505cb6255f356d4ffe44ba9665547740 split = String::split;
String[] names = split.apply("zhangsan,lisi,wangwu", ",");
System.out.println(Arrays.toString(names));

Stream 对象

概念

什么是 Stream ? 这里的 Stream 不同于 io 中的 InputStream 和 OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8 新加入的,Stream 只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。

Stream 的几个特征:

单次处理。一次处理结束后,当前Stream就关闭了。
支持并行操作
常见的获取 Stream 的方式

从集合中获取

Collection.stream();
Collection.parallelStream();

静态工厂

Arrays.stream(array)
Stream.of(T …)
IntStream.range()
这里只对 Stream 做简单的介绍,下面会有具体的应用。要说 Stream 与 Lambda 表达式有什么关系,其实并没有什么特别紧密的关系,只是 Lambda 表达式极大的方便了 Stream 的使用。如果没有 Lambda 表达式,使用 Stream 的过程中会产生大量的匿名类,非常别扭。

举例

以下的demo依赖于 Employee 对象,以及由 Employee 对象组成的 List 对象。

public class Employee {
 
  private String name;
  private String sex;
  private int age;
 
  public Employee(String name, String sex, int age) {
    super();
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  public String getName() {
    return name;
  }
 
  public String getSex() {
    return sex;
  }
  public int getAge() {
    return age;
  }
  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("Employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age)
        .append("}");
    return builder.toString();
  }
}
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("张三", "男", 25));
employees.add(new Employee("李四", "女", 24));
employees.add(new Employee("王五", "女", 23));
employees.add(new Employee("周六", "男", 22));
employees.add(new Employee("孙七", "女", 21));
employees.add(new Employee("刘八", "男", 20));

   

打印所有员工

Collection 提供了 forEach 方法,供我们逐个操作单个对象。

employees.forEach(e -> System.out.println(e)); 
或者
employees.stream().forEach(e -> System.out.println(e));

按年龄排序

Collections.sort(employees, (e1, e2) -> e1.getAge() - e2.getAge());
employees.forEach(e -> System.out.println(e));
或者
employees.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(e -> System.out.println(e));
打印年龄最大的女员工

max/min 返回指定排序条件下最大/最小的元素

Employee maxAgeFemaleEmployee = employees.stream()
    .filter(e -> "女".equals(e.getSex()))
    .max((e1, e2) -> e1.getAge() - e2.getAge())
    .get();
System.out.println(maxAgeFemaleEmployee);

   

打印出年龄大于20 的男员工

filter 可以过滤出符合条件的元素

employees.stream()
        .filter(e -> e.getAge() > 20 && "男".equals(e.getSex()))
        .forEach(e -> System.out.println(e));
打印出年龄最大的2名男员工

limit 方法截取有限的元素

employees.stream()
    .filter(e -> "男".equals(e.getSex()))
    .sorted((e1, e2) -> e2.getAge() - e1.getAge())
    .limit(2)
    .forEach(e -> System.out.println(e));

   

打印出所有男员工的姓名,使用 , 分隔

map 将 Stream 中所有元素的执行给定的函数后返回值组成新的 Stream

String maleEmployeesNames = employees.stream()
    .map(e -> e.getName())
    .collect(Collectors.joining(","));
System.out.println(maleEmployeesNames);

   

统计信息

IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics 包含了 Stream 中的汇总数据。

IntSummaryStatistics stat = employees.stream()
    .mapToInt(Employee::getAge).summaryStatistics();
System.out.println("员工总数:" + stat.getCount());
System.out.println("最高年龄:" + stat.getMax());
System.out.println("最小年龄:" + stat.getMin());
System.out.println("平均年龄:" + stat.getAverage());

   

总结

Lambda 表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。

以上就是对Java8 JavaLambda 的资料整理,后续继续补充相关资料谢谢大家对本站的支持!

更多Java Lambda 表达式详解及示例代码相关文章请关注PHP中文网!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JVM中的类加载程序子系统如何促进平台独立性?JVM中的类加载程序子系统如何促进平台独立性?Apr 23, 2025 am 12:14 AM

类加载器通过统一的类文件格式、动态加载、双亲委派模型和平台无关的字节码,确保Java程序在不同平台上的一致性和兼容性,实现平台独立性。

Java编译器会产生特定于平台的代码吗?解释。Java编译器会产生特定于平台的代码吗?解释。Apr 23, 2025 am 12:09 AM

Java编译器生成的代码是平台无关的,但最终执行的代码是平台特定的。1.Java源代码编译成平台无关的字节码。2.JVM将字节码转换为特定平台的机器码,确保跨平台运行但性能可能不同。

JVM如何处理不同操作系统的多线程?JVM如何处理不同操作系统的多线程?Apr 23, 2025 am 12:07 AM

多线程在现代编程中重要,因为它能提高程序的响应性和资源利用率,并处理复杂的并发任务。JVM通过线程映射、调度机制和同步锁机制,在不同操作系统上确保多线程的一致性和高效性。

在Java的背景下,'平台独立性”意味着什么?在Java的背景下,'平台独立性”意味着什么?Apr 23, 2025 am 12:05 AM

Java的平台独立性是指编写的代码可以在任何安装了JVM的平台上运行,无需修改。1)Java源代码编译成字节码,2)字节码由JVM解释执行,3)JVM提供内存管理和垃圾回收功能,确保程序在不同操作系统上运行。

Java应用程序仍然可以遇到平台特定的错误或问题吗?Java应用程序仍然可以遇到平台特定的错误或问题吗?Apr 23, 2025 am 12:03 AM

Javaapplicationscanindeedencounterplatform-specificissuesdespitetheJVM'sabstraction.Reasonsinclude:1)Nativecodeandlibraries,2)Operatingsystemdifferences,3)JVMimplementationvariations,and4)Hardwaredependencies.Tomitigatethese,developersshould:1)Conduc

云计算如何影响Java平台独立性的重要性?云计算如何影响Java平台独立性的重要性?Apr 22, 2025 pm 07:05 PM

云计算显着提升了Java的平台独立性。 1)Java代码编译为字节码,由JVM在不同操作系统上执行,确保跨平台运行。 2)使用Docker和Kubernetes部署Java应用,提高可移植性和可扩展性。

Java的平台独立性在广泛采用中扮演着什么角色?Java的平台独立性在广泛采用中扮演着什么角色?Apr 22, 2025 pm 06:53 PM

Java'splatformindependenceallowsdeveloperstowritecodeonceandrunitonanydeviceorOSwithaJVM.Thisisachievedthroughcompilingtobytecode,whichtheJVMinterpretsorcompilesatruntime.ThisfeaturehassignificantlyboostedJava'sadoptionduetocross-platformdeployment,s

容器化技术(例如Docker)如何影响Java平台独立性的重要性?容器化技术(例如Docker)如何影响Java平台独立性的重要性?Apr 22, 2025 pm 06:49 PM

容器化技术如Docker增强而非替代Java的平台独立性。1)确保跨环境的一致性,2)管理依赖性,包括特定JVM版本,3)简化部署过程,使Java应用更具适应性和易管理性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用