最新版 Lambda 表达式|万字笔记

文章目录


学习 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:就像转换或处理数据。
  • BiConsumerBiFunction:处理两个输入的场景。

通过这些类比,你可以更形象地理解这些接口的作用,并在实际编程中灵活使用它们。

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 表达式来加深理解。

猜你喜欢

转载自blog.csdn.net/qq_41340258/article/details/141752432