目录
一、内置函数式接口由来
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。

二、JDK 8 常用内置函数式接口介绍
它们主要在 java.util.function 包中,下面是最常用的几个接口:
1. Supplier接口

2. Consumer接口
3. Function接口

4. Predicate接口
三、Supplier接口
Supplier接口是一个函数式接口,用于表示一个不接受参数但可以提供一个结果的生产者。
3.2. 使用方法
java.util.function.Supplier<T> 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

供给型接口,通过Supplier接口中的get方法可以得到一个值,无参有返回的接口。
使用Lambda表达式返回数组元素最大值
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。

3.2. 使用场景
1. 无参回执/懒加载利器/惰性初始化
在某些情况下,你可能希望只有在真正需要结果时才进行计算或生成值。使用Supplier接口,你可以将计算或生成值的逻辑封装在get()方法中,并在需要时调用它。


这种延迟加载机制避免了不必要的开销,提升了性能。
2. 提供默认值
当某个方法需要返回一个值,但此时没有合适的值可返回时,可以使用Supplier接口来提供一个默认值。也可以使用Supplier接口来处理异常情况,通过Supplier抛出异常。


3.与Stream API结合生成无限数据流
在Java 8的Stream API中,Supplier接口可以与Stream.generate()方法一起使用,以生成一个无限流,其中每个元素都是通过调用Supplier的get()方法获得的。

4. 用于随机数、时间戳等动态数据的生成

5. 优化 if else
Supplier与Optional和工厂模式结合,优化业务代码中的if else语句





6. 更高级用法
除了上述基本用法外,Supplier还可以与其他Java函数式接口结合使用,如Function和Predicate等,以实现更复杂的逻辑。例如,我们可以使用Supplier和Predicate来创建一个可重试的方法调用机制:

7. 自定义 Supplier,简化业务逻辑
比如不同级别或单位的用户,我们发送不同的祝福短信。根据不同需求,指定不同的业务处理逻辑。

四、Consumer接口
java.util.function.Consumer<T> 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
4.1. 使用方法

使用Lambda表达式将一个字符串转成大写和小写的字符串
Consumer消费型接口,可以拿到accept方法参数传递过来的数据进行处理, 有参无返回的接口。基本使用如:

默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:

备注: java.util.Objects 的 requireNonNull 静态方法将会在参数为null时主动抛出 NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是“一步接一步”操作。例如两个步骤组合的情况,如下代码将字符串先转小写,再转大写,相当于单独对字符串做了两次处理,一次从"Hello World"转"hello world",另一次从"Hello World"转"HELLO WORLD":

4.2. 使用场景
1. 数据处理和转换
在数据处理过程中,Consumer接口常用于对数据进行操作,例如修改对象的状态、输出信息或传递给其他方法进行进一步处理。例如,在处理集合时,可以使用Consumer接口对集合中的每个元素执行特定操作。
修改对象的状态
package org.example;
import java.util.function.Consumer;
class Person {
// 姓名
private String name;
// 年龄
private int age;
// 座右铭
private String motto;
public Person() {
}
public Person(String name, int age, String motto) {
this.name = name;
this.age = age;
this.motto = motto;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setMotto(String motto) {
this.motto = motto;
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person("王哲晓", 30, "坚持专注某领域,是一件很酷的事");
Consumer<Person> updateName = p -> p.setName("wangzhexiao");
updateName.accept(person);
}
}
在集合中的使用
Consumer 常用于集合的元素遍历中,如使用 forEach 方法。
package org.example;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
List<String> favourites = Arrays.asList("Coding", "骑行", "国漫", "音乐", "收纳", "烹饪");
Consumer<String> printConsumer = System.out::println;
favourites.forEach(printConsumer);
}
}
2. 链式操作
Consumer接口支持链式操作,通过andThen方法可以将多个Consumer组合在一起,顺序执行。这种特性使得在处理复杂的数据处理流程时更加灵活和高效。
package org.example;
import java.util.function.Consumer;
class Person {
// 姓名
private String name;
// 年龄
private int age;
// 座右铭
private String motto;
public Person() {}
public Person(String name, int age, String motto) {
this.name = name;
this.age = age;
this.motto = motto;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setMotto(String motto) {
this.motto = motto;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "', motto=" + motto + '}';
}
}
public class Test {
public static void main(String[] args) {
Person person = new Person("王哲晓", 30, "坚持专注某领域,是一件很酷的事");
Consumer<Person> updateName = p -> p.setName("wangzhexiao");
Consumer<Person> updateMotto = p -> p.setMotto("来人世一遭,努力实现自我的价值");
updateName.accept(person);
updateMotto.accept(person);
// Person{name='wangzhexiao', age=30', motto=来人世一遭,努力实现自我的价值}
System.out.println(person);
}
}
3. 日志记录
在需要记录日志的场景中,可以使用Consumer接口来处理日志信息。例如,在某个操作完成后,使用Consumer接口将日志信息输出到控制台或日志文件中。
package org.example;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
@FunctionalInterface
interface LogConsumer extends Consumer<String> {
static LogConsumer forLevel(Level level) {
return message -> getLogger().log(level, message);
}
static Logger getLogger() {
return Logger.getAnonymousLogger();
}
}
public class Test {
public static void main(String[] args) {
LogConsumer infoConsumer = LogConsumer.forLevel(Level.INFO);
infoConsumer.accept("这是一条信息级别的日志");
LogConsumer errorConsumer = LogConsumer.forLevel(Level.SEVERE);
errorConsumer.accept("这是一条错误级别的日志");
}
}
4. 条件判断
虽然Consumer接口本身不直接用于条件判断,但在某些情况下,可以结合其他函数式接口(如Predicate)来实现条件判断后的数据处理。例如,先使用Predicate接口判断数据是否满足特定条件,然后使用Consumer接口对满足条件的数据进行处理。
5. 自定义业务复杂操作
对集合计算求和
package org.example;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 123, 3432, 42, 11);
// 定义一个内部类
class Sum {
private int total = 0;
public void add(int number) {
total += number;
}
public int getTotal() {
return total;
}
}
Sum sum = new Sum();
Consumer<Integer> sumConsumer = sum::add;
// 循环遍历求和
numbers.forEach(sumConsumer);
// 输出: Sum: 3618
System.out.println("Sum: " + sum.getTotal());
}
}
一万条业务数据批量入库(每10条)并统计入库数量
package org.example;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) throws Exception {
test();
}
public static void test() throws Exception {
// 初始化一万条模拟数据
List<User> userList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
User user = new User();
user.setName("我叫" + i);
userList.add(user);
}
if (userList.isEmpty()) throw new Exception();
// 构建一个数据入库并统计入库数据量的Consumer函数式接口
Consumer<List<User>> consumer = Test::importData;
// 构建消费者类,并传入函数式接口及数据列表
new DataConsumer(consumer).handlerData(userList);
}
public static void importData(List<User> list) {
// 假设这块先处理了数据入库操作
System.out.println("输出入库数据记录总数:" + list.size());
}
}
class DataConsumer<T> {
public Consumer<List<T>> importService;
public DataConsumer(Consumer<List<T>> importService) throws Exception {
this.importService = importService;
}
public void handlerData(List<T> list) {
// 模拟解析,每达到10条数据就进行回调入库/统计操作
List<T> dataList = new ArrayList<>();
for (T t : list) {
dataList.add(t);
if (dataList.size() == 1000) {
// 回调保存数据
saveData(dataList);
}
}
// 回调保存数据
saveData(dataList);
}
private void saveData(List<T> dataList) {
if (!dataList.isEmpty()) {
importService.accept(dataList);
dataList.clear();
}
}
}
class User {
private String name;
public User() {}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
五、Function接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。
5.1. 使用方法

使用Lambda表达式将字符串转成数字
Function转换型接口,对apply方法传入的T类型数据进行处理,返回R类型的结果,有参有返回的接口。例如:将 String 类型转换为 Integer 类型。

默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作。JDK源代码如:

该方法同样用于“先做什么,再做什么”的场景,和 Consumer 中的 andThen 差不多:

第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过 andThen 按照前后顺序组合到了一起。
注意,Function的前置条件泛型和后置条件泛型可以相同。
5.2. 使用场景
1. 个性化逻辑数据处理
我们可以按照自身的多种业务需求,对数据进行不同的加工处理,比如可以对相同的业务数据进行加减乘除操作:
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
}
public static void useCalculator(Calculator calculator, int a, int b) {
int result = calculator.calculate(a, b);
System.out.println("结果是:" + result);
}
public static void main(String[] args) {
useCalculator((a, b) -> a + b, 5, 3);
useCalculator((a, b) -> a * b, 5, 3);
}
2. 并发编程示例
package org.example;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
// 这里可以替换成实际项目中的业务逻辑
Function<String, Integer> stringToLengthFunction = String::length;
for(int i = 0; i< 100; i++) {
Random random = new Random();
int num = random.nextInt(100000);
String input = "Task-" + num;
executor.submit(() -> System.out.println(stringToLengthFunction.apply(input)));
}
executor.shutdown();
}
}
3. 对集合的操作
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 定义一个Function函数式接口集合,针对字符串进行各种不同的处理逻辑
List<Function<String, String>> functions = Arrays.asList(
s -> s.toUpperCase(),
s -> s.toLowerCase(),
s -> s.substring(0, 1)
);
String input = "Hello World!";
// 循环遍历集合,不同函数式接口对字符串进行处理后输出结果
functions.forEach(f -> System.out.println(f.apply(input)));
}
}
六、Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果,这时可以使用
java.util.function.Predicate<T> 接口
6.1. 使用方法
使用Lambda判断一个人名如果超过3个字就认为是很长的名字
对test方法的参数T进行判断,返回boolean类型的结果。用于条件判断的场景:

条件判断的标准是传入的Lambda表达式逻辑,只要名称长度大于3则认为很长。
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and 。其JDK源码为:

使用Lambda表达式判断一个字符串中即包含W,也包含H
使用Lambda表达式判断一个字符串中包含W或者包含H
使用Lambda表达式判断一个字符串中即不包含W
如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

默认方法:or
使用Lambda表达式判断一个字符串中包含W或者包含H与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:

如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:

默认方法:negate
使用Lambda表达式判断一个字符串中即不包含W
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:

从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样:

6.2. 使用场景
1. 作为过滤条件
package org.example;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<String> mottos = Arrays.asList(
"每天要足够努力,方能肩负起更多责任",
"每天要足够努力,方能不负此生",
"只要学不死,就往死里学",
"专注于一件事,持续地去努力,你会看到自身的价值和潜能的!");
// 创建一个Predicate来检查座右铭长度
Predicate<String> isLongContent = motto -> motto.length() > 16;
// 使用Predicate过滤名字列表
List<String> list = mottos.stream()
.filter(isLongContent)
.collect(Collectors.toList());
// 打印座右铭
list.forEach(System.out::println);
}
}
2. 与Supplier结合实现重试机制
除了上述基本用法外,Predicate还可以与其他Java函数式接口结合使用,如Supplier,以实现更复杂的逻辑。例如,我们可以使用Supplier和Predicate来创建一个可重试的方法调用机制:

468

被折叠的 条评论
为什么被折叠?



