一、背景
1、简介
在Java 8中,随着函数式接口(Functional Interface)的引入,我们迎来了一系列新的编程范式和工具。其中,Supplier
和Consumer
是两个非常基础且常用的函数式接口。对于初学者来说,理解并学会使用它们,对于提升编程能力和代码质量有着不可忽视的作用。
Java 8引入了函数式接口的概念后,使得Java编程更加灵活和简洁。其中,Supplier
和Consumer
是Java 8中两个非常基础且常用的函数式接口。它们分别代表了数据的提供者和消费者,为我们在编程中处理数据提供了极大的便利。
下面,我用通俗易懂的语言,结合丰富的应用场景和案例,来讲解这两个接口。
2、生活故事
想象一下,我们有一个神奇的餐厅,叫做“函数式餐厅”。在这个餐厅里,有两个特别的角色:Supplier
大厨和Consumer
食客。
Supplier
大厨是个非常勤劳的家伙,他整天在厨房里忙碌,准备各种美味佳肴。但他有一个特点,就是只负责做菜,不负责送到你的桌子上。他总是默默地站在厨房的某个角落,手里端着一盘盘诱人的菜肴,等着有人来取。
而Consumer
食客呢,他是个特别挑剔的家伙。他来到餐厅,不会直接点菜,而是告诉服务员他想吃什么类型的菜。服务员就会去找Supplier
大厨,让大厨准备这道菜。当大厨做好菜后,Consumer
食客就会走过来,品尝大厨的手艺,然后给出他的评价。
在这个餐厅里,Supplier
和Consumer
是两个非常重要的角色。没有Supplier
大厨,就没有美味的菜肴;没有Consumer
食客,大厨的手艺就无法得到认可和赞赏。
其实,在计算机编程的世界里,Supplier
和Consumer
也是两个非常重要的概念。Supplier
就像那个默默在厨房里准备菜肴的大厨,它负责提供数据或者结果,但不关心这些数据或结果是如何被使用的。而Consumer
就像那个挑剔的食客,它负责接收数据或结果,并对它们进行处理或消费。
所以,当你听到Supplier
和Consumer
这两个词时,就可以想象它们在“函数式餐厅”里忙碌的身影,一个负责制作,一个负责品尝。这样,是不是觉得它们变得亲切又有趣了呢?
正文开始
二、Supplier接口
Supplier接口是一个函数式接口,它表示一个不接受任何参数,但能够产生某种类型结果的操作。你可以把它想象成一个生产东西的公司,你不需要给它提供任何原材料,它就能生产出你想要的东西。Supplier
接口是一个没有参数的函数式接口,它只有一个get
方法,用于返回某种类型的数据。你可以将Supplier
想象成一个生产数据的公司,每次调用get
方法时,它就会生产并返回一个新的数据。
1. Supplier接口的定义
Supplier接口在Java 8中定义如下:
package java.util.function;
/**
* Represents a supplier of results.
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #get()}.
*
* @param <T> the type of results supplied by this supplier
*
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Supplier接口中只有一个无参的get()方法,它返回泛型类型T的结果。
2. Supplier接口的应用场景
场景一:随机数生成
假设我们需要生成一个指定范围的随机数,可以使用Supplier
来封装随机数生成的逻辑。
import java.util.Random;
import java.util.function.Supplier;
public class RandomNumberSupplier {
public static void main(String[] args) {
Supplier<Integer> randomNumberSupplier = () -> new Random().nextInt(100);
System.out.println(randomNumberSupplier.get()); // 输出一个0到99之间的随机数
}
}
场景二:创建对象
在创建对象时,我们可以使用Supplier
来封装对象的创建逻辑,这样可以使代码更加清晰和易于维护。
import java.util.function.Supplier;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
public static void main(String[] args) {
Supplier<User> userSupplier = () -> new User("张三", 25);
User user = userSupplier.get();
System.out.println(user); // 输出:User{name='张三', age=25}
}
}
场景三:结合Stream API使用
Supplier
可以与Java 8的Stream API结合使用,用于生成Stream的数据源。
import java.util.function.Supplier;
import java.util.stream.Stream;
public class StreamWithSupplier {
public static void main(String[] args) {
Supplier<Integer> numberSupplier = () -> (int) (Math.random() * 100);
Stream<Integer> numberStream = Stream.generate(numberSupplier);
numberStream.limit(5).forEach(System.out::println); // 输出5个随机数
}
}
三、Consumer接口
Consumer接口也是一个函数式接口,它表示一个接受单一输入参数并且不返回任何结果的操作。你可以把它想象成一个消费者,你给它一个东西,它消费掉这个东西,但不给你任何回报。
1. Consumer接口的定义
Consumer接口在Java 8中定义如下:
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
Consumer接口中定义了一个accept方法,它接受一个泛型类型T的参数,并且没有返回值(void)。此外,它还提供了一个默认方法andThen,允许将多个Consumer串联起来,形成一个操作链。
2. Consumer接口的应用场景
场景一:打印数据
当我们需要打印某个数据时,可以使用Consumer
来封装打印的逻辑。
import java.util.function.Consumer;
public class PrintConsumer {
public static void main(String[] args) {
Consumer<String> printConsumer = System.out::println;
printConsumer.accept("Hello, World!"); // 输出:Hello, World!
}
}
场景二:数据验证
在处理数据时,我们可能需要验证数据的合法性。使用Consumer
可以方便地封装验证逻辑。
import java.util.function.Consumer;
public class ValidationConsumer {
public static void main(String[] args) {
String data = "12345";
Consumer<String> validationConsumer = s -> {
if (s.length() < 5) {
throw new IllegalArgumentException("数据长度不足");
}
// 其他验证逻辑...
};
try {
validationConsumer.accept(data);
System.out.println("数据验证通过");
} catch (IllegalArgumentException e) {
System.out.println("数据验证失败:" + e.getMessage());
}
}
}
**场景三:**修改集合元素
对于集合中的每个元素,我们可能需要执行一些修改操作。使用Consumer
可以方便地对集合中的元素进行处理。
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class ListModificationWithConsumer {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
Consumer<Integer> multiplyByTwo = n -> n *= 2;
numbers.forEach(multiplyByTwo);
numbers.forEach(System.out::println); // 输出:2, 4, 6
}
}
在这个例子中,我们定义了一个Consumer
,它接受一个整数并将其乘以2。然后,我们使用forEach
方法将这个操作应用于列表中的每个元素。
四、Supplier接口与Consumer接口与匿名内部类
在Java中,Supplier
、Consumer
和匿名内部类都是编程工具,它们可以帮助我们更简洁、灵活地编写代码。
首先,我们来回顾一下这三个概念:
- 匿名内部类:是一个没有名字的内部类,通常用于实现一个接口或继承一个类,而不需要单独定义一个类。它常常用于临时实现某个接口或扩展某个类的功能。
- Supplier:是一个函数式接口,它有一个方法
get()
,用于提供(或者说生成)数据。你可以把Supplier
想象成一个“数据工厂”,它负责按需生成数据。 - Consumer:也是一个函数式接口,它有一个方法
accept()
,用于消费(或者说处理)数据。你可以把Consumer
想象成一个“数据处理器”,它负责接收数据并执行某种操作。
现在,让我们用一个例子来说明它们之间的关系:
假设我们有一个简单的需求:从某个数据源获取一个整数,并将这个整数打印出来。
使用匿名内部类的方式,我们可以这样实现:
// 使用匿名内部类实现Supplier和Consumer
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ExampleWithAnonymousClass {
public static void main(String[] args) {
// 使用匿名内部类实现Supplier
Supplier<Integer> numberSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
// 这里模拟从数据源获取一个整数
return 42;
}
};
// 使用匿名内部类实现Consumer
Consumer<Integer> numberConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer value) {
// 这里模拟打印整数
System.out.println("The number is: " + value);
}
};
// 使用Supplier获取数据,并使用Consumer处理数据
Integer number = numberSupplier.get();
numberConsumer.accept(number);
}
}
在这个例子中,我们分别使用匿名内部类实现了Supplier
和Consumer
接口。numberSupplier
负责提供数据(这里是硬编码的42),而numberConsumer
负责处理数据(这里是打印出来)。
然而,随着Java 8的推出,我们有了更简洁的方式来实现同样的功能,那就是使用Lambda表达式:
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ExampleWithLambda {
public static void main(String[] args) {
// 使用Lambda表达式实现Supplier
Supplier<Integer> numberSupplier = () -> 42;
// 使用Lambda表达式实现Consumer
Consumer<Integer> numberConsumer = value -> System.out.println("The number is: " + value);
// 使用Supplier获取数据,并使用Consumer处理数据
Integer number = numberSupplier.get();
numberConsumer.accept(number);
}
}
在这个例子中,我们使用了Lambda表达式来替换匿名内部类的实现。Lambda表达式让代码更简洁、更易于阅读。现在,我们不再需要写那么多冗余的代码来定义接口的实现,只需要一行代码就可以完成。
总结起来,Supplier
和Consumer
是函数式接口,它们提供了定义数据和数据处理逻辑的框架。而匿名内部类是Java中一种实现接口或继承类的手段,但在Java 8及以后的版本中,Lambda表达式通常是一种更简洁、更现代的实现方式。通过将这三者结合起来使用,我们可以编写出更加灵活和可维护的代码。
五、电商系统的应用举例
接下来,我通过电商系统中应用Supplier
和Consumer
接口的实际案例,旨在帮助您更好地理解和记忆这两个接口在业务场景中的应用。
案例一:用户积分更新(Consumer应用)
在电商系统中,Consumer接口在这里使得积分更新逻辑变得更加清晰和模块化,易于维护和测试。它允许开发者将用户积分更新的具体操作与用户购买行为的其他处理逻辑分离,提高代码的扩展性和可重用性。
// Consumer接口实现更新用户积分逻辑
Consumer<User> updateUserPoints = user -> {
// 假设calculatePoints是一个方法,根据用户购买行为计算积分
int pointsToAdd = PointsCalculator.calculatePoints(user.getPurchaseHistory());
user.addPoints(pointsToAdd);
// 更新用户积分到数据库
userService.updateUserPoints(user);
};
// 在用户完成购买后调用
usersWhoPurchased.forEach(updateUserPoints);
案例二:商品库存扣减(Consumer应用)
使用Consumer接口可以将库存扣减的逻辑封装起来,便于在不同的订单处理流程中重用。这种方式简化了代码结构,使得库存管理更加集中和一致。
// Consumer接口实现库存扣减逻辑
Consumer<Product> deductInventory = product -> {
// 检查库存
if (inventoryService.checkInventory(product) > 0) {
// 扣减库存
inventoryService.deductInventory(product);
}
};
// 在订单处理流程中调用,对订单中的每个商品扣减库存
order.getProducts().forEach(deductInventory);
我觉得这个本质就是给业务提供不同的扩展机制实现
案例三:动态获取商品价格(Supplier应用)
商品的价格可能会从不同的对象数据中获取,明显的动态变化。我们可以使用Supplier
接口来定义获取商品价格的逻辑。有点时候可能我们的商城系统有不同的价格对象,那么此时可以通过该接口返回不同对象的结果,也是OK的.
import java.util.function.Supplier;
public class ProductPriceProvider {
private double price;
public ProductPriceProvider(double initialPrice) {
this.price = initialPrice;
}
public double calcPrice(Supplier<Double> priceSupplier) {
// 使用Supplier获取价格,某些不同的逻辑 ,然后统一返回结果
return priceSupplier.get();
}
public void setPrice(double newPrice) {
this.price = newPrice;
}
public static void main(String[] args) {
ProductPriceProvider provider = new ProductPriceProvider(100.0);
provider.calcPrice(() -> { // 可以返回NormalUser对象的价格
});
provider.calcPrice(() -> { // 可以返回EmployeeUser对象的价格
});
}
}
通过这3个案例,大家是否感觉到一个接口和返回值有关,一个接口和循环有关,如果是,恭喜你应该有点意识了。
六、Supplier接口与Consumer接口的参数化传递
当涉及到参数化传递的场景时,Supplier
和Consumer
接口可以提供一种灵活的方式来处理数据和执行操作。案例如下:
案例一:使用Supplier进行参数化数据获取
import java.util.function.Supplier;
public class ParameterizedSupplierExample {
// 方法一:调用方法二,并传递一个Supplier作为参数
public static void methodOne() {
// 创建一个Supplier,用于提供数据
Supplier<String> dataSupplier = () -> "Data provided by Supplier";
// 调用方法二,并将Supplier作为参数传递
methodTwo(dataSupplier);
}
// 方法二:接受一个Supplier作为参数,并获取数据
public static void methodTwo(Supplier<String> dataSupplier) {
// 使用Supplier获取数据
String data = dataSupplier.get();
// 执行一些操作,比如打印数据
System.out.println("Data obtained from Supplier: " + data);
// 继续执行方法二的其他逻辑
// ...
}
public static void main(String[] args) {
methodOne(); // 调用方法一,触发整个流程
}
}
案例二:使用Consumer进行参数化操作执行
import java.util.function.Consumer;
public class ParameterizedConsumerExample {
// 方法一:调用方法二,并传递一个Consumer作为参数
public static void methodOne() {
// 创建一个Consumer,用于执行操作
Consumer<String> operationConsumer = message -> {
System.out.println("Executing operation on message: " + message);
// 这里可以添加更多的操作逻辑
};
// 调用方法二,并将Consumer作为参数传递
methodTwo("Hello, World!", operationConsumer);
}
// 方法二:接受一个字符串和一个Consumer作为参数,并执行操作
public static void methodTwo(String message, Consumer<String> operationConsumer) {
// 执行一些操作,比如打印消息
System.out.println("Processing message: " + message);
// 使用Consumer执行参数化操作
operationConsumer.accept(message);
// 继续执行方法二的其他逻辑
// ...
}
public static void main(String[] args) {
methodOne(); // 调用方法一,触发整个流程
}
}
在第一个例子中,methodOne
创建了一个Supplier
,它用于提供数据,并将其作为参数传递给methodTwo
。methodTwo
通过调用get
方法从Supplier
中获取数据,并执行后续操作。
在第二个例子中,methodOne
创建了一个Consumer
,它定义了要在消息上执行的操作,并将它连同消息一起传递给methodTwo
。methodTwo
首先执行一些处理逻辑,然后调用accept
方法,通过Consumer
执行参数化操作。
这两个例子展示了如何使用Supplier
和Consumer
作为参数化传递的接口,在方法间进行数据和操作的传递。这种模式允许更灵活和可重用的代码结构。
对于参数化传递,也可以看下这篇文章:blog.csdn.net/qq\_3660207…
通过这个参数化的传递,有没有感觉到一些设计模式的影子呢?
七、Supplier接口与Consumer接口与设计模式
Supplier
和Consumer
接口与设计模式之间还有点关系。设计模式是解决在软件设计中经常遇到的一类问题的最佳实践。Supplier
和Consumer
接口是Java 8引入的函数式接口,它们经常在设计模式中得到应用,使得代码更加简洁、灵活和可重用。
以下是几个设计模式中利用Supplier
和Consumer
接口的例子:
策略模式与Supplier
策略模式定义了一系列可以互相替换的算法,使得算法可以独立于使用它的客户端变化。当需要将不同的行为作为参数传递给方法时,可以使用Supplier
作为策略的表示。对于一些轻量级的设计模式的代码,用这个感觉也听方便的。
import java.util.function.Supplier;
public class StrategyPatternWithSupplier {
public static void executeStrategy(Supplier<String> strategy) {
String result = strategy.get(); // 执行策略
System.out.println("Strategy result: " + result);
}
public static void main(String[] args) {
// 创建不同的策略
Supplier<String> strategyOne = () -> "Strategy One Executed";
Supplier<String> strategyTwo = () -> "Strategy Two Executed";
// 执行策略
executeStrategy(strategyOne);
executeStrategy(strategyTwo);
}
}
消费者模式与Consumer
消费者模式通常与Consumer
接口一起使用,表示一种行为,它接受数据并对其进行处理,但不返回任何结果。这种模式在函数式编程和流式处理中很常见。
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerPatternExample {
public static void processData(List<String> data, Consumer<String> consumer) {
data.forEach(consumer::accept); // 对每个数据项应用消费者行为
}
public static void main(String[] args) {
List<String> dataList = Arrays.asList("A", "B", "C");
// 创建消费者行为
Consumer<String> printer = message -> System.out.println(message);
// 处理数据,应用消费者行为
processData(dataList, printer);
}
}
在这个例子中,processData
方法接受一个数据列表和一个Consumer
,并使用forEach
遍历列表,对每个元素应用Consumer
的行为(在这里是打印元素)。
适配器模式与Supplier和Consumer
适配器模式允许将一个类的接口转换成客户端所期望的另一种接口,从而使得原本不兼容的类可以一起工作。虽然适配器模式本身不直接涉及Supplier
和Consumer
,但可以使用它们来增强适配器的功能。
例如,假设有一个旧的API返回String
,而新的客户端期望使用Supplier<String>
。适配器可以将旧的API适配为新的接口:
public class OldApi {
public String getData() {
return "Data from Old API";
}
}
public class Adapter implements Supplier<String> {
private final OldApi oldApi;
public Adapter(OldApi oldApi) {
this.oldApi = oldApi;
}
@Override
public String get() {
return oldApi.getData();
}
}
现在客户端可以使用Supplier
接口而不是直接调用旧API:
public class Client {
public static void main(String[] args) {
OldApi oldApi = new OldApi();
Supplier<String> adapter = new Adapter(oldApi);
String data = adapter.get(); // 使用适配器获取数据
System.out.println(data);
}
}
这些例子展示了Supplier
和Consumer
如何在设计模式中发挥作用,帮助实现更加灵活和模块化的代码结构。通过利用这些函数式接口,我们可以更容易地传递行为和数据,从而简化代码并提高可重用性。
八、Supplier接口与Consumer接口的对比
描述:Supplier
接口用于提供生成一个值或对象,而Consumer
接口用于接收一个输入参数并执行某种操作,但不返回任何结果。
输入参数:Supplier
接口不接受任何输入参数,而Consumer
接口接受一个输入参数。
返回值:Supplier
接口返回一个结果,而Consumer
接口没有返回值(返回void
)。
应用场景:Supplier
常用于需要延迟计算或生成值的场景,比如当你不希望立即计算某个值,而是在需要时才进行计算。而Consumer
则常用于处理数据,例如遍历集合中的元素并对每个元素执行某些操作。
在实际编程中,我们可以根据具体需求选择使用这两个接口。例如,当我们需要生成一个随机数或获取某个资源时,可以使用Supplier
;而当我们需要遍历集合并对每个元素执行某种操作时,可以使用Consumer
。通过结合使用这两个接口,我们可以编写出更加灵活和可维护的代码。
九、学习方法
有没有感觉自己和很多人一样,就是学完了这个知识点,但是一直没在实际的项目中用到这两个接口,过了一阵子就忘记了,这该怎么办,怎么学习和上面这两个接口,能够让咱们彻底的掌握他们,试试如下方法:
理解核心概念
首先,确保你真正理解了Supplier
和Consumer
的核心概念。Supplier
是一个提供数据的接口,它不接收任何参数,并返回一个结果;而Consumer
是一个接收数据并对其进行处理的接口,它不返回任何结果。理解这两个接口的基本作用和使用场景是掌握它们的第一步。
编写小例子
尝试编写一些简单的小例子来应用这两个接口。例如,你可以创建一个Supplier
来生成随机数,或者创建一个Consumer
来打印字符串。通过编写这些例子,你可以更好地理解它们的用法和如何在实际代码中应用它们。
参与实际项目
如果有机会,尝试在实际项目中应用Supplier
和Consumer
。找到一些可以使用这两个接口的场景,并将它们融入到你的代码中。这样,你不仅可以在实践中巩固知识,还可以看到它们如何与其他代码和组件相互作用。
创造自己的场景
如果没有实际项目可用,你也可以尝试创造一些自己的场景来练习使用Supplier
和Consumer
。例如,你可以编写一个简单的计算器程序,使用Supplier
来提供操作数,使用Consumer
来处理计算结果。
参考优秀代码
查看一些优秀的开源项目或代码库,看看其他人是如何使用Supplier
和Consumer
的。这可以帮助你了解它们在实际应用中的最佳实践和常见用法。
持续复习和练习
定期回顾和复习你学过的知识是非常重要的。你可以创建一些复习笔记或练习题目,以便在需要时快速回顾和巩固。此外,参加编程社区或论坛的讨论,与其他人分享你的经验和问题,也是一个很好的学习方式。
关联实际应用
尝试将Supplier
和Consumer
与你熟悉的日常场景或问题联系起来。例如,你可以想象一个自助餐厅的场景,其中Supplier
是食物供应台,负责提供食物;而Consumer
是顾客,负责取走并享用食物。通过这种联想,你可以更容易地记住和理解它们的作用。
最后,记住学习是一个持续的过程,不要期望一蹴而就。通过不断地实践、复习和应用,你会逐渐掌握并熟练运用Supplier
和Consumer
这两个接口。
十、最后
其实很多开源已经用到了这两个接口,只是日常大家没有去关注而已,那如何发现呢?
其实很简单,你只需要在IDEA开发工具中,去看下Consumer接口和Supplier有哪些子类,就可以了,找到了第三方包中的涉及到这些子类的地方,你就会发现早已融入了我们工作中,同时这两个接口也是一种软件工程中的扩展点的设计,对于一些增强类型的逻辑处理,也可以这么设计,比如在RestTemplate中,有这样一个类:RequestFactoryCustomizer,一个非常明显的Customizer模式的类的玩法,代码如下:
private static class RequestFactoryCustomizer implements Consumer<ClientHttpRequestFactory> {
private final Duration connectTimeout;
private final Duration readTimeout;
private final Boolean bufferRequestBody;
RequestFactoryCustomizer() {
this((Duration)null, (Duration)null, (Boolean)null);
}
private RequestFactoryCustomizer(Duration connectTimeout, Duration readTimeout, Boolean bufferRequestBody) {
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.bufferRequestBody = bufferRequestBody;
}
RequestFactoryCustomizer connectTimeout(Duration connectTimeout) {
return new RequestFactoryCustomizer(connectTimeout, this.readTimeout, this.bufferRequestBody);
}
RequestFactoryCustomizer readTimeout(Duration readTimeout) {
return new RequestFactoryCustomizer(this.connectTimeout, readTimeout, this.bufferRequestBody);
}
RequestFactoryCustomizer bufferRequestBody(boolean bufferRequestBody) {
return new RequestFactoryCustomizer(this.connectTimeout, this.readTimeout, bufferRequestBody);
}
public void accept(ClientHttpRequestFactory requestFactory) {
ClientHttpRequestFactory unwrappedRequestFactory = this.unwrapRequestFactoryIfNecessary(requestFactory);
if (this.connectTimeout != null) {
this.setConnectTimeout(unwrappedRequestFactory);
}
if (this.readTimeout != null) {
this.setReadTimeout(unwrappedRequestFactory);
}
if (this.bufferRequestBody != null) {
this.setBufferRequestBody(unwrappedRequestFactory);
}
}
}
可以看到,对外提供了一个accept的方法,用于进行了自定义设置扩展。
本文到这里就结束了,如果对你有帮助,收藏、关注、分享、点赞哦