文章目录
学习 Lambda 表达式(Lambda Expressions)可以从以下几个步骤和概念入手,逐步构建你的知识网络:
1. 基本概念
1.1 Lambda 表达式的定义
是一种匿名函数,可以简化代码,常用于函数式编程。它们允许你将函数作为参数传递给其他函数。
1.2 快速搞懂 Lambda
理解 Lambda 表达式的基本概念,可以类比为在日常生活中使用快递服务。
1.2.1 快递服务类比
想象你需要给朋友发送一些东西,但你不想亲自去邮局。你可以使用快递服务,这时 Lambda 表达式就像是给快递员的指令。
1.2.2 传统方式(类比于传统的匿名内部类)
- 你需要:给朋友寄一个包裹。
- 传统做法:你写一封信,详细说明如何打包、如何送达等步骤,然后交给快递员。
// 传统的匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Sending a package to friend.");
}
}).start();
1.2.3 Lambda 表达式方式
- 你需要:给朋友寄一个包裹。
- Lambda 做法:你直接告诉快递员:“把这个包裹送给我的朋友。”,省去了写详细说明的步骤。
// 使用Lambda表达式
new Thread(() -> System.out.println("Sending a package to friend.")).start();
1.2.4 形象解释
- 快递员:相当于 Java 中的 Thread 或其他需要函数作为参数的类。
- 你给出的指令:就是 Lambda 表达式。
- 传统方式:你写一封详细的信(匿名内部类),里面详细说明如何处理包裹。
- Lambda 方式:你直接告诉快递员怎么做(Lambda 表达式),简洁明了。
1.2.5 具体案例
假设你想给朋友寄一个生日礼物:
- 传统方式:你写一封信,里面详细说明:“请把这个包裹打包好,贴上地址,然后送到这个地址。” 这就像是匿名内部类,你定义了一个完整的类来实现这个任务。
- Lambda方式:你直接告诉快递员:“请把这个包裹送给我的朋友。” 这就是Lambda表达式,你直接给出了一个简洁的指令。
1.2.6 代码实现
假设你有一个 GiftSender 接口,它只有一个方法 sendGift():
interface GiftSender {
void sendGift();
}
- 传统方式:
GiftSender sender = new GiftSender() {
@Override
public void sendGift() {
System.out.println("Sending a birthday gift to friend.");
}
};
sender.sendGift();
- Lambda方式:
GiftSender sender = () -> System.out.println("Sending a birthday gift to friend.");
sender.sendGift();
总结
- Lambda 表达式就像是直接给快递员的简洁指令,省去了写详细说明的步骤。
- 传统匿名内部类就像是写一封信,详细说明每一步骤。
通过这种类比,你可以更形象地理解Lambda表达式是如何简化代码的。它让你能够直接给出指令,而不是定义一个完整的类来实现这个简单的任务。
2. 函数式接口
2.1 函数式接口的定义
只有一个抽象方法的接口。Lambda 表达式通常用于实现这些接口的唯一抽象方法。
- 例子:Runnable, Callable, Consumer, Supplier, Predicate 等。
2.2 快速搞懂函数式接口
理解 Lambda 函数式接口,可以类比为在餐馆点菜的过程。
2.2.1 餐馆点菜类比
想象你走进了一家自助餐馆,你可以选择不同的服务方式:
2.2.2 传统服务方式(类比于传统方法)
- 服务员:给你菜单,你点菜,服务员去厨房取菜。
- 菜单:上面有各种菜品,服务员根据你的选择去准备。
2.2.3 自助餐方式(类比于 Lambda 函数式接口)
- 自助餐台:你直接去取你想要的食物。
- 函数式接口:就像是自助餐台上的指示牌,告诉你可以做什么。
2.2.4 形象解释
假设你想点一份沙拉:
- 传统服务:
- 你告诉服务员:“请给我一份沙拉。”(你直接给出了命令)
- 服务员去厨房,厨师准备沙拉,服务员端给你。
- 自助餐:
- 你走到沙拉吧台(函数式接口),那里有指示牌:“请自己取沙拉。”(函数式接口的抽象方法)
- 你自己动手取沙拉(实现这个接口的方法)。
假设有一个FoodPreparer接口,它只有一个抽象方法 prepareFood():
interface FoodPreparer {
void prepareFood();
}
传统方式
就像你告诉服务员你想要什么:
// 传统方式
FoodPreparer preparer = new FoodPreparer() {
@Override
public void prepareFood() {
System.out.println("Preparing a salad.");
}
};
preparer.prepareFood();
Lambda 方式
就像你直接去自助餐台:
// Lambda方式
FoodPreparer preparer = () -> System.out.println("Preparing a salad.");
preparer.prepareFood();
2.2.5 具体案例
假设你想创建一个简单的应用,让用户可以选择不同的操作:
- 操作选择:就像餐馆的菜单,你可以选择不同的菜品(操作)。
interface Operation {
void execute();
}
- 使用 Lambda 表达式:
// 定义一个操作
Operation add = () -> System.out.println("Adding numbers");
// 执行这个操作
add.execute();
总结
- 函数式接口就像是自助餐台上的指示牌,它告诉你可以做什么,但不告诉你具体怎么做。
- Lambda 表达式就像是你根据指示牌的指引去做的事情,简化了代码,减少了样板代码。
通过这种类比,你可以形象地理解:
- 函数式接口提供了一个标准化的方式让你实现一个操作,就像自助餐台提供了一个标准化的取食方式。
- Lambda 表达式让你可以非常简洁地定义这个操作,就像你直接去取食物一样简单。
3. Lambda 表达式的语法
-
基本语法:
(参数列表) -> { 代码块 }
- 例如:(int x) -> System.out.println(x);
-
参数类型推断:如果编译器可以推断出参数类型,可以省略类型。
(x) -> System.out.println(x);
-
单参数简写:如果只有一个参数,可以省略括号。
x -> System.out.println(x);
-
无参数:
() -> System.out.println("Hello");
-
返回值:如果只有一行代码且是返回值,可以省略 return 和花括号。
x -> x * x; // 等同于 (x) -> { return x * x; }
4. Lambda 表达式的使用场景
4.1 两大使用场景
-
作为方法参数:
Arrays.asList("a", "b", "c").forEach(e -> System.out.println(e));
-
实现函数式接口:
Runnable r = () -> System.out.println("My Runnable");
4.2 具体案例
4.2.1. 作为方法参数
01 图书馆借书系统
- 场景:你想从图书馆借书,但图书馆的借书系统需要你提供一个回调函数来通知你借书结果。
- 传统方式:你需要实现一个接口来接收借书结果。
// 传统方式
class BookBorrowResult implements BorrowCallback {
@Override
public void onBorrowResult(String result) {
System.out.println("Borrow result: " + result);
}
}
Library.borrowBook("Java Programming", new BookBorrowResult());
- Lambda 方式:你直接给出回调函数。
// Lambda方式
Library.borrowBook("Java Programming", result -> System.out.println("Borrow result: " + result));
02 咖啡店点单系统
- 场景:你想在咖啡店点一杯咖啡,系统需要你提供一个回调函数来通知你咖啡准备好。
- 传统方式:
class CoffeeReadyListener implements CoffeeCallback {
@Override
public void onCoffeeReady() {
System.out.println("Your coffee is ready!");
}
}
CoffeeShop.orderCoffee("Espresso", new CoffeeReadyListener());
- Lambda 方式:
CoffeeShop.orderCoffee("Espresso", () -> System.out.println("Your coffee is ready!"));
4.2.2. 实现函数式接口
01 智能家居系统
- 场景:你想让智能灯在特定时间自动打开。
- 传统方式:
class LightOn implements Runnable {
@Override
public void run() {
System.out.println("Turning the light on at 7 PM.");
}
}
Timer timer = new Timer();
timer.schedule(new LightOn(), 7 * 60 * 60 * 1000); // 7 PM
- Lambda 方式:
Timer timer = new Timer();
timer.schedule(() -> System.out.println("Turning the light on at 7 PM."), 7 * 60 * 60 * 1000);
02 文件处理系统
- 场景:你想读取一个文件并打印每一行。
- 传统方式:
class FileProcessor implements LineProcessor {
@Override
public void processLine(String line) {
System.out.println("Line read: " + line);
}
}
// 假设有一个FileReader类
FileReader reader = new FileReader("example.txt");
reader.processFile(new FileProcessor());
- Lambda 方式:
FileReader reader = new FileReader("example.txt");
reader.processFile(line -> System.out.println("Line read: " + line));
4.2.3 总结
- 作为方法参数:Lambda 表达式可以直接作为函数的参数传递,就像你给系统一个简短的指令,告诉它在特定条件下应该做什么。例子中,图书馆借书和咖啡店点单系统都是这种情况。
- 实现函数式接口:Lambda 表达式可以直接实现函数式接口的唯一抽象方法,就像你给智能设备一个指令,让它在特定时间执行某个任务。例子中,智能家居系统和文件处理系统都是这种情况。
通过这些类比,你可以更形象地理解:
- Lambda 表达式如何简化代码,使得代码更加简洁和直观。
- 函数式接口和方法参数的概念,如何在实际编程中应用Lambda表达式来提高代码的可读性和可维护性。
4.3 更多案例
理解Lambda表达式的使用场景,可以通过日常生活中的一些任务来类比。以下是一些形象的例子:
01 订阅报纸
- 传统方式:你需要亲自去报社订阅报纸,每次都要填写详细信息。
- Lambda 方式:就像你给报社留了一个简短的指令:“每周一给我送报纸。”,报社会根据这个指令自动执行。
// 传统方式
class NewspaperSubscriber implements Runnable {
@Override
public void run() {
System.out.println("Delivering newspaper weekly.");
}
}
new Thread(new NewspaperSubscriber()).start();
// Lambda方式
new Thread(() -> System.out.println("Delivering newspaper weekly.")).start();
02 自动浇水
- 传统方式:你需要设置一个定时器,每天提醒你去给植物浇水。
- Lambda方式:就像你设置了一个智能灌溉系统,只需要告诉它:“每天早上6点给植物浇水。”
// 传统方式
class WaterPlants implements Runnable {
@Override
public void run() {
System.out.println("Watering plants at 6 AM.");
}
}
Timer timer = new Timer();
timer.schedule(new WaterPlants(), 6 * 60 * 60 * 1000); // 6 AM
// Lambda方式
Timer timer = new Timer();
timer.schedule(() -> System.out.println("Watering plants at 6 AM."), 6 * 60 * 60 * 1000);
03 处理邮件
- 传统方式:你需要写一个程序,每次收到邮件时,你都要手动检查并处理。
- Lambda 方式:就像你告诉邮局:“当有邮件时,立即通知我。”
// 传统方式
class EmailProcessor implements Runnable {
@Override
public void run() {
System.out.println("Processing new email.");
}
}
// 假设有一个邮件系统
EmailSystem emailSystem = new EmailSystem();
emailSystem.setEmailListener(new EmailProcessor());
// Lambda方式
EmailSystem emailSystem = new EmailSystem();
emailSystem.setEmailListener(() -> System.out.println("Processing new email."));
04 过滤列表
- 传统方式:你需要遍历一个列表,检查每个元素是否符合条件,然后手动构建一个新列表。
- Lambda方式:就像你告诉助手:“找出所有大于10的数字。”
// 传统方式
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 10, 15, 20);
List<Integer> filteredList = new ArrayList<>();
for (Integer number : numbers) {
if (number > 10) {
filteredList.add(number);
}
}
// Lambda方式
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 10, 15, 20);
List<Integer> filteredList = numbers.stream()
.filter(x -> x > 10)
.collect(Collectors.toList());
05 事件监听
- 传统方式:你需要实现一个接口来监听按钮点击事件。
- Lambda方式:就像你告诉按钮:“当我被点击时,显示一个消息。”
// 传统方式
JButton button = new JButton("Click me");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
// Lambda方式
JButton button = new JButton("Click me");
button.addActionListener(e -> System.out.println("Button clicked!"));
总结
这些例子展示了Lambda表达式如何简化代码:
- Lambda表达式就像是给系统或设备的简短指令,告诉它在特定条件下应该做什么。
- 传统方式需要定义完整的类或方法来实现这些任务,而Lambda表达式直接给出了任务的具体实现。
通过这些类比,你可以更形象地理解Lambda表达式的使用场景:它们用于简化代码,提供直接的、简洁的任务实现。
5. 方法引用
5.1 Lambda 的方法引用
方法引用(Method Reference)是一种简化 Lambda 表达式的写法,可以直接引用现有的方法或构造函数。
- 类名::静态方法
- 对象::实例方法
- 类名::实例方法(适用于任意对象)
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println); // 调用System.out.println方法
5.2 快速搞懂 Lambda 的方法引用
5.2.1 快捷方式类比
想象你经常需要访问一个远方的朋友,但你不想每次都输入完整的地址。
- 传统方式:每次你都要完整地输入朋友的地址。
- 快捷方式:你创建了一个快捷方式,只需要点击这个快捷方式就能直接到达朋友家。
5.2.2 形象解释
假设你有一个朋友名叫 Alice,每次你想给她发消息,你都要完整地输入她的地址。
- 传统方式:每次都输入 Alice 的地址。
- 快捷方式:你创建了一个名为“Alice”的快捷方式,直接点击它就能发消息。
5.2.3 具体使用
在编程中,Lambda 方法引用就像是创建了一个快捷方式,直接引用现有的方法。
场景:打印所有字符串
- 传统 Lambda:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
- 方法引用:
names.forEach(System.out::println);
- 传统 Lambda:就像每次你都要完整地输入 Alice 的地址来发消息。
- 方法引用:就像你创建了一个快捷方式,直接点击它就能发消息。
5.2.4 更具体的例子
01 构造函数引用
- 传统 Lambda:创建一个新的对象。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Person> people = names.stream()
.map(name -> new Person(name))
.collect(Collectors.toList());
- 方法引用:直接引用构造函数。
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
02 静态方法引用
- 传统Lambda:调用一个静态方法。
List<String> strings = Arrays.asList("hello", "world");
List<String> upperCaseStrings = strings.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
- 方法引用:直接引用静态方法。
List<String> upperCaseStrings = strings.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
03 实例方法引用
- 传统 Lambda:调用对象的实例方法。
List<String> strings = Arrays.asList("hello", "world");
strings.forEach(s -> System.out.println(s));
- 方法引用:直接引用实例方法。
strings.forEach(System.out::println);
总结
- 方法引用就像是创建了一个快捷方式,让你能够直接引用现有的方法或构造函数,而不需要每次都写完整的Lambda表达式。
通过这种类比,你可以更形象地理解:
- 方法引用如何简化代码,使得代码更加简洁和直观。
- Lambda表达式和方法引用之间的关系,就像是手动输入地址和使用快捷方式的关系。
6. 函数式接口的常用接口
6.1 常见的接口
-
Consumer:接受一个输入参数,不返回值。
Consumer<String> consumer = s -> System.out.println(s);
-
Supplier:不接受参数,返回一个结果。
Supplier<String> supplier = () -> "Hello";
-
Predicate:接受一个参数,返回一个布尔值。
Predicate<String> predicate = s -> s.length() > 5;
-
Function:接受一个参数,返回一个结果。
Function<String, Integer> toInteger = Integer::valueOf;
6.2 具体案例
深入理解和使用Lambda函数式接口的常用接口,可以通过一些日常生活中的场景来类比。以下是一些形象的例子:
01 Consumer 接口
- Consumer:接受一个输入参数,不返回结果,就像是给出指令但不需要反馈。
例子:通知服务
- 场景:你想通知所有朋友一个消息。
- 传统方式:
class NotificationService {
public void notify(String message) {
System.out.println("Notifying: " + message);
}
}
NotificationService service = new NotificationService();
service.notify("Party tonight!");
- Lambda 方式:
List<String> friends = Arrays.asList("Alice", "Bob", "Charlie");
friends.forEach(friend -> System.out.println("Notifying " + friend + ": Party tonight!"));
- 方法引用:
List<String> friends = Arrays.asList("Alice", "Bob", "Charlie");
Consumer<String> notify = System.out::println;
friends.forEach(friend -> notify.accept("Notifying " + friend + ": Party tonight!"));
02 Supplier 接口
- Supplier:不接受参数,返回一个结果,就像是请求一个信息。
例子:天气预报
- 场景:你想查看今天的天气。
- 传统方式:
class WeatherService {
public String getWeather() {
return "Sunny";
}
}
WeatherService service = new WeatherService();
String weather = service.getWeather();
System.out.println("Today's weather: " + weather);
- Lambda方式:
Supplier<String> weatherSupplier = () -> "Sunny";
String weather = weatherSupplier.get();
System.out.println("Today's weather: " + weather);
03 Predicate 接口
- Predicate:接受一个参数,返回一个布尔值,就像是做一个判断。
例子:筛选朋友
- 场景:你想找出所有名字长度大于5的朋友。
- 传统方式:
class FriendFilter {
public boolean isNameLongEnough(String name) {
return name.length() > 5;
}
}
List<String> friends = Arrays.asList("Alice", "Bob", "Charlie", "David");
FriendFilter filter = new FriendFilter();
List<String> longNameFriends = friends.stream()
.filter(filter::isNameLongEnough)
.collect(Collectors.toList());
- Lambda方式:
List<String> friends = Arrays.asList("Alice", "Bob", "Charlie", "David");
Predicate<String> longNamePredicate = name -> name.length() > 5;
List<String> longNameFriends = friends.stream()
.filter(longNamePredicate)
.collect(Collectors.toList());
04 Function 接口
- Function:接受一个参数,返回一个结果,就像是将一种东西转换成另一种。
例子:转换货币
- 场景:你想将美元转换成欧元。
- 传统方式:
class CurrencyConverter {
public double convertToEuro(double dollars) {
return dollars * 0.85; // 假设汇率为0.85
}
}
CurrencyConverter converter = new CurrencyConverter();
double euros = converter.convertToEuro(100);
System.out.println("100 USD in EUR: " + euros);
- Lambda方式:
Function<Double, Double> toEuro = dollars -> dollars * 0.85;
double euros = toEuro.apply(100.0);
System.out.println("100 USD in EUR: " + euros);
05 BiConsumer 接口
- BiConsumer:接受两个参数,不返回结果,就像是同时处理两个东西。
例子:通知朋友和时间
- 场景:你想通知朋友在某个时间点做某事。
- 传统方式:
class NotificationService {
public void notify(String friend, String time) {
System.out.println(friend + " should be ready at " + time);
}
}
NotificationService service = new NotificationService();
service.notify("Alice", "8 PM");
- Lambda方式:
BiConsumer<String, String> notify = (friend, time) -> System.out.println(friend + " should be ready at " + time);
notify.accept("Alice", "8 PM");
06 BiFunction 接口
- BiFunction:接受两个参数,返回一个结果,就像是基于两个输入生成一个输出。
例子:计算朋友聚会费用
- 场景:你想计算每个朋友的聚会费用,根据他们吃的食物数量和单价。
- 传统方式:
class PartyCostCalculator {
public double calculateCost(int foodQuantity, double pricePerItem) {
return foodQuantity * pricePerItem;
}
}
PartyCostCalculator calculator = new PartyCostCalculator();
double cost = calculator.calculateCost(3, 5.99);
System.out.println("Cost: $" + cost);
- Lambda 方式:
BiFunction<Integer, Double, Double> calculateCost = (quantity, price) -> quantity * price;
double cost = calculateCost.apply(3, 5.99);
System.out.println("Cost: $" + cost);
总结
这些例子展示了如何通过日常生活中的场景来理解和使用Lambda函数式接口:
- Consumer:就像通知或处理信息。
- Supplier:就像请求或提供信息。
- Predicate:就像做判断或筛选。
- Function:就像转换或处理数据。
- BiConsumer 和 BiFunction:处理两个输入的场景。
通过这些类比,你可以更形象地理解这些接口的作用,并在实际编程中灵活使用它们。
7. 流(Stream API)
7.1 理解 Stream
用于处理集合数据的 API,常与 Lambda 表达式结合使用。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().filter(x -> x % 2 == 0).mapToInt(x -> x).sum();
7.2 具体案例
理解和使用 Lambda 流(Stream API)可以类比为在现实生活中处理一系列物品或信息的流程。
01 图书馆书籍管理
- 场景:你想从图书馆的藏书中筛选出所有计算机类书籍。
- 传统方式:
List<Book> books = Arrays.asList(
new Book("Java Programming", "Computer"),
new Book("1984", "Fiction"),
new Book("Algorithms", "Computer")
);
List<Book> computerBooks = new ArrayList<>();
for (Book book : books) {
if ("Computer".equals(book.getCategory())) {
computerBooks.add(book);
}
}
- 使用 Stream API:
List<Book> books = Arrays.asList(
new Book("Java Programming", "Computer"),
new Book("1984", "Fiction"),
new Book("Algorithms", "Computer")
);
List<Book> computerBooks = books.stream()
.filter(book -> "Computer".equals(book.getCategory()))
.collect(Collectors.toList());
02 咖啡店订单处理
- 场景:你想找出所有价格超过5美元的咖啡订单。
- 传统方式:
List<Order> orders = Arrays.asList(
new Order("Espresso", 3.5),
new Order("Latte", 4.5),
new Order("Mocha", 6.0)
);
List<Order> expensiveOrders = new ArrayList<>();
for (Order order : orders) {
if (order.getPrice() > 5) {
expensiveOrders.add(order);
}
}
- 使用 Stream API:
List<Order> orders = Arrays.asList(
new Order("Espresso", 3.5),
new Order("Latte", 4.5),
new Order("Mocha", 6.0)
);
List<Order> expensiveOrders = orders.stream()
.filter(order -> order.getPrice() > 5)
.collect(Collectors.toList());
03 学生成绩分析
- 场景:你想找出所有及格的学生,并计算他们的平均分。
- 传统方式:
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 70),
new Student("Charlie", 60)
);
List<Student> passedStudents = new ArrayList<>();
double sum = 0;
int count = 0;
for (Student student : students) {
if (student.getScore() >= 60) {
passedStudents.add(student);
sum += student.getScore();
count++;
}
}
double average = count > 0 ? sum / count : 0;
- 使用 Stream API:
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 70),
new Student("Charlie", 60)
);
double average = students.stream()
.filter(student -> student.getScore() >= 60)
.mapToInt(Student::getScore)
.average()
.orElse(0);
04 文件系统搜索
- 场景:你想找出某个目录下所有大于 1MB 的文件。
- 传统方式:
File directory = new File("/path/to/directory");
File[] files = directory.listFiles();
List<File> largeFiles = new ArrayList<>();
for (File file : files) {
if (file.isFile() && file.length() > 1_000_000) {
largeFiles.add(file);
}
}
- 使用 Stream API:
File directory = new File("/path/to/directory");
List<File> largeFiles = Arrays.stream(directory.listFiles())
.filter(File::isFile)
.filter(file -> file.length() > 1_000_000)
.collect(Collectors.toList());
05 数据清洗和转换
- 场景:你有一组字符串数据,想去掉空格并转换成大写。
- 传统方式:
List<String> data = Arrays.asList(" hello ", " world ", " java ");
List<String> cleanedData = new ArrayList<>();
for (String s : data) {
cleanedData.add(s.trim().toUpperCase());
}
- 使用 Stream API:
List<String> data = Arrays.asList(" hello ", " world ", " java ");
List<String> cleanedData = data.stream()
.map(String::trim)
.map(String::toUpperCase)
.collect(Collectors.toList());
总结
这些例子展示了如何通过日常生活中的场景来理解和使用Stream API:
- 图书馆书籍管理:筛选特定类别的书籍。
- 咖啡店订单处理:找出满足条件的订单。
- 学生成绩分析:过滤及格学生并计算平均分。
- 文件系统搜索:查找满足条件的文件。
- 数据清洗和转换:对数据进行清理和转换。
通过这些类比,你可以更形象地理解 Stream API 的作用:
- 流就像是一条生产线,每个操作(如filter, map, collect)都是生产线上的一个工序。
- Lambda表达式则定义了每个工序的具体操作。
Stream API 简化了数据处理,使得代码更加简洁、易读,并且可以更方便地进行并行处理。
8. Lambda 表达式与并行处理
8.1 理解并行流
利用多核处理器提高性能。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream().filter(x -> x % 2 == 0).mapToInt(x -> x).sum();
8.2 具体案例
理解和使用 Lambda 表达式与并行处理,可以类比为在现实生活中进行大规模任务的分发和处理。
01 图书馆书籍分类
- 场景:你有一大堆书籍需要分类到不同的架子上。
- 传统方式(单线程处理):
List<Book> books = Arrays.asList(
new Book("Java Programming", "Computer"),
new Book("1984", "Fiction"),
new Book("Algorithms", "Computer")
);
Map<String, List<Book>> categorizedBooks = new HashMap<>();
for (Book book : books) {
String category = book.getCategory();
if (!categorizedBooks.containsKey(category)) {
categorizedBooks.put(category, new ArrayList<>());
}
categorizedBooks.get(category).add(book);
}
- 并行处理:
List<Book> books = Arrays.asList(
new Book("Java Programming", "Computer"),
new Book("1984", "Fiction"),
new Book("Algorithms", "Computer")
);
Map<String, List<Book>> categorizedBooks = books.parallelStream()
.collect(Collectors.groupingBy(Book::getCategory));
02 咖啡店订单处理
- 场景:你有一大批订单需要快速处理。
- 传统方式(单线程处理):
List<Order> orders = Arrays.asList(
new Order("Espresso", 3.5),
new Order("Latte", 4.5),
new Order("Mocha", 6.0)
);
double totalPrice = 0;
for (Order order : orders) {
totalPrice += order.getPrice();
}
- 并行处理:
List<Order> orders = Arrays.asList(
new Order("Espresso", 3.5),
new Order("Latte", 4.5),
new Order("Mocha", 6.0)
);
double totalPrice = orders.parallelStream()
.mapToDouble(Order::getPrice)
.sum();
03 数据中心文件搜索
- 场景:你需要在一个大型数据中心中搜索所有大于1GB的文件。
- 传统方式(单线程处理):
List<File> files = Arrays.asList(new File("file1"), new File("file2"), new File("file3"));
List<File> largeFiles = new ArrayList<>();
for (File file : files) {
if (file.length() > 1_000_000_000) {
largeFiles.add(file);
}
}
- 并行处理:
List<File> files = Arrays.asList(new File("file1"), new File("file2"), new File("file3"));
List<File> largeFiles = files.parallelStream()
.filter(file -> file.length() > 1_000_000_000)
.collect(Collectors.toList());
04 社交网络用户分析
- 场景:你需要分析数百万用户的数据,找出活跃用户。
- 传统方式(单线程处理):
List<User> users = Arrays.asList(
new User("Alice", 100), // 活跃度
new User("Bob", 20),
new User("Charlie", 500)
);
List<User> activeUsers = new ArrayList<>();
for (User user : users) {
if (user.getActivity() > 100) {
activeUsers.add(user);
}
}
- 并行处理:
List<User> users = Arrays.asList(
new User("Alice", 100),
new User("Bob", 20),
new User("Charlie", 500)
);
List<User> activeUsers = users.parallelStream()
.filter(user -> user.getActivity() > 100)
.collect(Collectors.toList());
05 数据清洗和分析
- 场景:你有一大批数据需要清洗并进行统计分析。
- 传统方式(单线程处理):
List<String> data = Arrays.asList(" hello ", " world ", " java ");
List<String> cleanedData = new ArrayList<>();
for (String s : data) {
cleanedData.add(s.trim().toUpperCase());
}
int count = 0;
for (String s : cleanedData) {
if (s.contains("O")) {
count++;
}
}
- 并行处理:
List<String> data = Arrays.asList(" hello ", " world ", " java ");
long count = data.parallelStream()
.map(String::trim)
.map(String::toUpperCase)
.filter(s -> s.contains("O"))
.count();
总结
这些例子展示了如何通过日常生活中的场景来理解和使用Lambda表达式与并行处理:
- 图书馆书籍分类:并行处理可以加速分类过程。
- 咖啡店订单处理:快速计算总价。
- 数据中心文件搜索:并行搜索大文件。
- 社交网络用户分析:快速找出活跃用户。
- 数据清洗和分析:并行清洗数据并进行统计。
通过这些类比,你可以更形象地理解:
- 并行流(parallelStream)就像是多个工人同时处理任务,而不是一个接一个地处理。
- Lambda表达式定义了每个任务的具体操作。
并行处理通过分发任务到多个处理单元(如CPU核心),可以显著提高大规模数据处理的效率。特别是在处理大量数据时,Lambda表达式与并行流的结合可以极大地提升程序的性能。
9. Lambda 表达式与异常
9.1 Lambda 异常处理
Lambda 表达式中处理异常需要使用 try-catch 块。
Runnable r = () -> {
try {
// 代码可能抛出异常
} catch (Exception e) {
// 异常处理
}
};
9.2 具体案例
理解和使用 Lambda 表达式与异常处理,可以类比为在日常生活中处理突发情况或错误。
01 图书馆书籍借阅
- 场景:你想借一本书,但可能书不在库存中。
- 传统方式(使用try-catch):
class Library {
public Book borrowBook(String title) throws BookNotFoundException {
// 模拟借书过程
if (!books.contains(title)) {
throw new BookNotFoundException("Book not found: " + title);
}
return new Book(title);
}
}
// 使用
try {
Book book = library.borrowBook("Java Programming");
System.out.println("Borrowed: " + book.getTitle());
} catch (BookNotFoundException e) {
System.out.println("Error: " + e.getMessage());
}
- Lambda方式(异常处理在Lambda内部):
Optional<Book> book = Optional.ofNullable(library.borrowBook("Java Programming"))
.map(b -> {
try {
return b;
} catch (BookNotFoundException e) {
System.out.println("Error: " + e.getMessage());
return null;
}
});
book.ifPresent(b -> System.out.println("Borrowed: " + b.getTitle()));
02 网络请求处理
- 场景:你想从多个 API 获取数据,但这些请求可能失败。
- 传统方式:
List<String> apiUrls = Arrays.asList("api1", "api2", "api3");
for (String url : apiUrls) {
try {
String response = fetchDataFromAPI(url);
System.out.println("Data from " + url + ": " + response);
} catch (IOException e) {
System.out.println("Failed to fetch data from " + url + ": " + e.getMessage());
}
}
- Lambda 方式:
List<String> apiUrls = Arrays.asList("api1", "api2", "api3");
apiUrls.forEach(url -> {
try {
String response = fetchDataFromAPI(url);
System.out.println("Data from " + url + ": " + response);
} catch (IOException e) {
System.out.println("Failed to fetch data from " + url + ": " + e.getMessage());
}
});
03 文件读取
- 场景:你想读取多个文件的内容,但某些文件可能无法读取。
- 传统方式:
List<String> filePaths = Arrays.asList("file1.txt", "file2.txt", "file3.txt");
for (String path : filePaths) {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Line from " + path + ": " + line);
}
} catch (IOException e) {
System.out.println("Failed to read " + path + ": " + e.getMessage());
}
}
- Lambda方式:
List<String> filePaths = Arrays.asList("file1.txt", "file2.txt", "file3.txt");
filePaths.forEach(path -> {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Line from " + path + ": " + line);
}
} catch (IOException e) {
System.out.println("Failed to read " + path + ": " + e.getMessage());
}
});
04 数据转换与异常
- 场景:你想将一组字符串转换为整数,但某些字符串可能不是有效的数字。
- 传统方式:
List<String> numbers = Arrays.asList("1", "2", "three", "4");
List<Integer> intNumbers = new ArrayList<>();
for (String num : numbers) {
try {
intNumbers.add(Integer.parseInt(num));
} catch (NumberFormatException e) {
System.out.println("Invalid number: " + num);
}
}
- Lambda方式:
List<String> numbers = Arrays.asList("1", "2", "three", "4");
List<Integer> intNumbers = numbers.stream()
.map(num -> {
try {
return Integer.parseInt(num);
} catch (NumberFormatException e) {
System.out.println("Invalid number: " + num);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
总结
这些例子展示了如何通过日常生活中的场景来理解和使用Lambda表达式与异常处理:
- 图书馆书籍借阅:处理书籍可能不存在的情况。
- 网络请求处理:处理网络请求可能失败的情况。
- 文件读取:处理文件可能无法读取的情况。
- 数据转换与异常:处理数据转换过程中可能出现的异常。
通过这些类比,你可以更形象地理解:
- Lambda表达式可以直接包含异常处理
10. Lambda 总结
- 基础:理解Lambda表达式是什么。
- 语法:掌握Lambda表达式的写法。
- 函数式接口:知道Lambda表达式常与哪些接口一起使用。
- 使用场景:如何在实际代码中应用Lambda表达式。
- 方法引用:简化Lambda表达式的写法。
- Stream API:结合Lambda表达式进行数据处理。
- 并行处理:利用Lambda表达式提高程序效率。
- 异常处理:在Lambda表达式中如何处理异常。
通过这些步骤,你可以从零基础逐步理解和掌握 Lambda 表达式的使用。记住,实践是最好的学习方式,尝试在你的项目中使用 Lambda 表达式来加深理解。