Java是一门强大的面向对象的语言,除了8种基本的数据类型,其他一切皆为对象。因此,在Java中定义函数或方法都离不开对象,也就意味着很难直接将方法或函数像参数一样传递,而Java8中的Lambda表达式解决了这个问题。

一、为什么需要Lambda

简单的来说,引入Lambda就是为了简化代码,允许把函数作为一个方法的参数传递进方法中。

1.1 真的简化了?

示例:如果想把某个接口的实现类作为参数传递给一个方法会怎么做?

  • Java8以前
public static void general() {
// 用匿名内部类的方式来创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("公众号:风尘博客!");
}
}).run();
}
  • Lambda 写法
public static void lambda() {
// 使用Lambda来创建线程
new Thread(() -> System.out.println("公众号:风尘博客!")).run();
}

1.2 Lambda表达式是什么?

Java中,将方法作为参数进行传递的方式被称为Lambda表达式

1.3 Lambda 表达式语法结构

Lambda其实是一个箭头函数,也可称为匿名函数:->

箭头操作符将Lambda表达式分成了两部分:

  1. 左侧:Lambda表达式的参数列表(接口中抽象方法的参数列表)
  2. 右侧:Lambda表达式中所需执行的功能(Lambda体,对抽象方法的实现)

1.4 语法格式

  • 无参,无返回值,Lambda 体只需一条语句。
public static void noParam() {
Runnable r1 = () -> System.out.println("noParam Test!");
r1.run();
}
  • Lambda 需要一个参数,参数的小括号可以省略。
public static void oneParam() {
// Consumer<String> con = (s) -> System.out.println(s);
// 参数的小括号可以省略。
Consumer<String> con = s -> System.out.println(s);
con.accept("oneParam Test!");
}
  • Lambda 需要多个参数,并且有返回值。
public static void params() {
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
// 比较x/y的大小
return Integer.compare(x, y);
};
System.out.println(com.compare(1, 2));
}
  • Lambda 体只有一条语句时,return 与大括号可以省略。
public static void one() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
System.out.println(com.compare(1, 2));
}

上面几条示例好像有一个共性:参数列表的数据类型都没写,这是为什么呢?

1.5 类型推断

Lambda 表达式中的参数类型都是由编译器推断得出的。

public static void typeInference() {
//Integer 类型可以省略
Comparator<Integer> com = (Integer x,Integer y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
// 类型推断
BinaryOperator<Long> addImplicit = (x, y) -> x + y;
}

Lambda 表达式中无需指定类型,程序依然可 以编译,这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。

1.6 小节

Lambda表达式使得Java拥有了函数式编程的能力,但在JavaLambda表达式是对象,它必须依附于一类特别的对象类型——函数式接口(functional interface)。

二、函数式接口

函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。可以有多个默认方法,静态方法。

JDK8java.util.function 中定义了几个标准的函数式接口,供我们使用。

2.1 Java 内置四大核心函数式接口

函数式接口 参数类型 返回类型 用途
Consumer<T> T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier<T> T 返回类型为T的对象,包 含方法:T get();
Function<T,R> T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate<T> T boolean 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t);
  • 消费型接口

void accept(T t);

consumerDemo(3, s -> System.out.println(s * 3));

public static void consumerDemo(Integer value, Consumer<Integer> consumer) {
consumer.accept(value);
}
  • 供给型接口

T get();

// 生成10个以内的随机书
List<Integer> numList = supplierDemo(10, () -> (int)(100 * Math.random()));
System.out.println(numList); public static List<Integer> supplierDemo(int num, Supplier<Integer> supplier) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = supplier.get();
list.add(n);
}
return list;
}
  • 函数型接口

R apply(T t);

// 处理字符串
String str1 = functionDemo("Hello!风尘博客", s -> s.substring(6));
System.out.println(str1);
String str2 = functionDemo("vanDusty", s -> s.toUpperCase());
System.out.println(str2); public static String functionDemo(String str, Function<String, String> function) {
return function.apply(str);
}
  • 断言型接口

boolean test(T t);

// 将满足条件的字符串放入集合
List<String> list = Arrays.asList("hello", "van", "function", "predicate");
List<String> newList = predicateDemo(list, s -> s.length() > 5);
System.out.println(newList); public static List<String> predicateDemo(List<String> list, Predicate<String> predicate) {
List<String> newList = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)) {
newList.add(s);
}
}
return newList;
}

2.2 自定义函数式接口

我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

// 字符串转大写
String newStr = selfFunctionalInterface((str) -> str.toUpperCase(), "abc");
System.out.println(newStr); public static String selfFunctionalInterface(SelfFunctionalInterface<String> selfFunctionalInterface, String str) {
return selfFunctionalInterface.getValue(str);
}

三、方法引用和构造器引用

3.1 方法引用

方法引用是指通过方法的名字来指向一个方法。

3.1.1 方法引用使用的前提条件是什么呢?

  1. 方法引用所引用的方法的参数列表必须要和函数式接口中抽象方法的参数列表相同(完全一致);
  2. 方法引用所引用的方法的的返回值必须要和函数式接口中抽象方法的返回值相同(完全一致)。

3.1.2 方法引用三种格式

  • 实例对象名::实例方法名
private static void instanceMethod() {
UserDomain user = new UserDomain(1L, "Van"); Supplier<String> sup = () -> user.getUserName();
System.out.println(sup.get()); // 等同于
Supplier<String> supplier = user::getUserName;
System.out.println(supplier.get());
}
  • 类名::静态方法名
private static void staticMethod() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
System.out.println(com.compare(3,9)); // 等同于
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(3,9));
}
  • 类名::实例方法名
private static void instanceMethodObject() {
UserDomain user = new UserDomain(1L, "Van"); Function<UserDomain, String> fun = (e) -> e.getUserName();
System.out.println(fun.apply(user)); // 等同于
Function<UserDomain, String> fun2 = UserDomain::getUserName;
System.out.println(fun2.apply(user));
}

3.2 构造器引用

  1. 前提:构造器参数列表要与接口中抽象方法的参数列表一致!
  2. 语法格式:类名 :: new
  • 构造器引用
private static void object() {
// UserDomain 中必须有一个 UserDomain(String userName) 的构造器,下同
Function<String,UserDomain> fun = (n) -> new UserDomain(n);
fun.apply("Van"); System.out.println("===等价于===");
Function<String,UserDomain> function = UserDomain::new;
function.apply("Van"); // 带两个参数的构造器引用就要用BiFunction,多个参数的话,还可以自定义一个这样的函数式接口
BiConsumer<Long, String> biConsumer = UserDomain :: new;
biConsumer.accept(1L,"Van");
}
  • 数组引用
private static void array() {
//传统Lambda实现
Function<Integer,int[]> function = (i) -> new int[i];
int[] apply = function.apply(10);
System.out.println(apply.length); //数组类型引用实现
function = int[] ::new;
apply = function.apply(100);
System.out.println(apply.length);
}

四、 总结

Github 示例代码

Lambda表达式是Java对于函数式编程的温和转变,面向对象编程和函数式编程不是互相对立的,结合使用能够更加有效地帮助我们管理程序的复杂性。

技术交流

  1. 风尘博客
  2. 风尘博客-掘金
  3. 风尘博客-博客园
  4. Github

必知必会之Lambda表达式的更多相关文章

  1. 必知必会之Java注解

    必知必会之Java注解 目录 不定期更新中-- 元注解 @Documented @Indexed @Retention @Target 常用注解 @Deprecated @FunctionalInte ...

  2. 《MySQL 必知必会》读书总结

    这是 <MySQL 必知必会> 的读书总结.也是自己整理的常用操作的参考手册. 使用 MySQL 连接到 MySQL shell>mysql -u root -p Enter pas ...

  3. SQL 必知必会

    本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...

  4. 《MySQL必知必会》[01] 基本查询

    <MySQL必知必会>(点击查看详情) 1.写在前面的话 这本书是一本MySQL的经典入门书籍,小小的一本,也受到众多网友推荐.之前自己学习的时候是啃的清华大学出版社的计算机系列教材< ...

  5. mysql必知必会

    春节放假没事,找了本电子书mysql必知必会敲了下.用的工具是有道笔记的markdown文档类型. 下面是根据大纲已经敲完的章节,可复制到有道笔记的查看,更美观. # 第一章 了解SQL## 什么是S ...

  6. 《MySQL必知必会》整理

    目录 第1章 了解数据库 1.1 数据库基础 1.1.1 什么是数据库 1.1.2 表 1.1.3 列和数据类型 1.1.4 行 1.1.5 主键 1.2 什么是SQL 第2章 MySQL简介 2.1 ...

  7. msql 必知必会笔记

    Edit Mysql 必知必会 第一章 理解SQL 什么是数据库 数据库(database) 保存有组织的数据的容器 什么是表  一组特定类型的数据的结构化清单 什么是模式  数据库和表的布局及特性的 ...

  8. 《SQL必知必会》笔记

    SQL必知必会(第4版) 作者:[美]Ben Forta 本书介绍了sql在不同数据库工具(Oracle.SQLite.SQL server.MySQL.MariaDB.PostgreSQL...)是 ...

  9. SQL 必知必会 总结(一)

    SQL必知必会 总结(一) 第 1 课 了解SQL 1.数据库(database): 保存有组织的数据容器(通常是一个文件或一组文件). 2.数据库管理系统(DBMS): 数据库软件,数据库是通过 D ...

  10. MySql必知必会实战练习(二)数据检索

    在上篇博客MySql必知必会实战练习(一)表创建和数据添加中完成了各表的创建和数据添加,下面进行数据检索和过滤操作. 1. Select子句使用顺序 select--->DISTINCT---& ...

随机推荐

  1. ENS 域名注册表智能合约(ENSRegistry.sol)解析

    ENS 注册表合约是 ENS 系统中的核心合约,了解这个合约可以敲开我们理解 ENS 域名系统的大门. 打开下面的折叠区域可以查看用 Solidity 语言编写的详细代码.当前部署在以太坊中的 ENS ...

  2. HashMap 源码赏析 JDK8

    一.简介 HashMap源码看过无数遍了,但是总是忘,好记性不如烂笔头. 本文HashMap源码基于JDK8. 文章将全面介绍HashMap的源码及HashMap存在的诸多问题. 开局一张图,先来看看 ...

  3. 2018 CCPC 网络赛

    The Power Cube is used as a stash of Exotic Power. There are n cities numbered 1,2,…,n where allowed ...

  4. MQ如何解决消息的顺序问题和消息的重复问题?

    一.摘要 分布式消息系统作为实现分布式系统可扩展.可伸缩性的关键组件,需要具有高吞吐量.高可用等特点.而谈到消息系统的设计,就回避不了两个问题: 1.消息的顺序问题 2.消息的重复问题 二.关键特性以 ...

  5. Nest.js你学不会系列-初识Nest

    前言 最近在学习研究 Nest 框架,但是在学习过程中除了参考翻阅官方文档外国内几乎没有多少资料能系统的讲解 Nest 的相关内容,所以打算想通过我自己学习的角度讲解下 Nest 框架,不知道能坚持多 ...

  6. springboot 报错nested exception is java.lang.IllegalStateException: Failed to check the status of the service xxxService No provider available for the service

    spring: dubbo:#关闭所有服务的启动时检查:(没有提供者时报错) consumer: check: false timeout: 3000

  7. javaweb-codereview 学习记录-3

    Class类加载流程 实际上就是ClassLoader将会调用loadclass来尝试加载类,首先将会在jvm中尝试加载我们想要加载的类,如果jvm中没有的话,将调用自身的findclass,此时要是 ...

  8. typescript step by step interface class

  9. 51Nod 1238 - 最小公倍数之和 V3(毒瘤数学+杜教筛)

    题目 戳这里 推导 ∑i=1n∑j=1nlcm(i,j)~~~\sum_{i=1}^{n}\sum_{j=1}^{n}lcm(i,j)   ∑i=1n​∑j=1n​lcm(i,j) =∑i=1n∑j= ...

  10. ArrayList.subList方法使用总结

    ArrayList.subList方法使用总结 示例 List<String> list=new ArrayList<>(); list.add("d"); ...