文章目錄
- map()基礎:一對一的數據轉換
- map()的工作原理
- 方法引用讓代碼更簡潔
- 對象轉換:實際業務應用
- 用戶信息轉換示例
- 特殊類型的map():mapToInt、mapToLong、mapToDouble
- 鏈式map():多重轉換
- map()與filter()組合:數據處理管道
- 實戰案例:訂單數據處理系統
- 本章小結
想象一下如果處理一批用戶數據,需要把用戶對象轉換成不同的格式:有時需要提取姓名列表,有時需要計算年齡,有時需要格式化顯示信息。用傳統方式,得寫好多個循環:
// 提取姓名
List<String> names = new ArrayList<>();
for (User user : users) {names.add(user.getName());
}// 計算年齡
List<Integer> ages = new ArrayList<>();
for (User user : users) {ages.add(calculateAge(user.getBirthYear()));
}
傳統方式很麻煩麻煩,用Stream的map()
方法一行代碼就能搞定數據轉換。
List<String> names = users.stream().map(User::getName).collect(toList());
List<Integer> ages = users.stream().map(user -> calculateAge(user.getBirthYear())).collect(toList());
今天我們就來學習map()
方法,這個數據轉換的"變形金剛",看看它如何讓數據轉換變得簡單優雅!
map()基礎:一對一的數據轉換
map()的工作原理
map()
方法對流中的每個元素應用一個函數,將原始元素轉換為新的元素。它是一對一的映射關系:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class MapBasics {public static void main(String[] args) {System.out.println("=== map()基礎用法 ===");List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 1. 數字平方轉換System.out.println("原數據: " + numbers);List<Integer> squares = numbers.stream().map(n -> n * n) // 每個數字轉換為它的平方.collect(Collectors.toList());System.out.println("平方后: " + squares);// 2. 數字轉字符串List<String> numberStrings = numbers.stream().map(n -> "數字" + n) // 轉換為字符串格式.collect(Collectors.toList());System.out.println("轉字符串: " + numberStrings);// 3. 類型轉換List<Double> doubles = numbers.stream().map(n -> n.doubleValue()) // int轉double.collect(Collectors.toList());System.out.println("轉double: " + doubles);// 4. 字符串轉換List<String> words = Arrays.asList("hello", "world", "java");List<String> upperWords = words.stream().map(String::toUpperCase) // 方法引用:轉大寫.collect(Collectors.toList());System.out.println("轉大寫: " + upperWords);// 5. 字符串長度轉換List<Integer> lengths = words.stream().map(String::length) // 提取字符串長度.collect(Collectors.toList());System.out.println("字符串長度: " + lengths);}
}
輸出結果:
=== map()基礎用法 ===
原數據: [1, 2, 3, 4, 5]
平方后: [1, 4, 9, 16, 25]
轉字符串: [數字1, 數字2, 數字3, 數字4, 數字5]
轉double: [1.0, 2.0, 3.0, 4.0, 5.0]
轉大寫: [HELLO, WORLD, JAVA]
字符串長度: [5, 5, 4]
💡 關鍵理解:map()
不會改變流中元素的數量,只是改變元素的類型或值。輸入5個元素,輸出也是5個元素。
方法引用讓代碼更簡潔
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class MethodReferenceMap {public static void main(String[] args) {System.out.println("=== 方法引用簡化map() ===");List<String> words = Arrays.asList("apple", "banana", "cherry");// Lambda表達式方式System.out.println("Lambda表達式:");List<String> result1 = words.stream().map(word -> word.toUpperCase()).collect(Collectors.toList());System.out.println("結果: " + result1);// 方法引用方式(更簡潔)System.out.println("\n方法引用:");List<String> result2 = words.stream().map(String::toUpperCase) // 等價于 word -> word.toUpperCase().collect(Collectors.toList());System.out.println("結果: " + result2);// 靜態方法引用List<Integer> numbers = Arrays.asList(-3, -1, 0, 2, 5);System.out.println("\n靜態方法引用:");List<Integer> absolutes = numbers.stream().map(Math::abs) // 等價于 n -> Math.abs(n).collect(Collectors.toList());System.out.println("原數據: " + numbers);System.out.println("絕對值: " + absolutes);}
}
輸出結果:
=== 方法引用簡化map() ===
Lambda表達式:
結果: [APPLE, BANANA, CHERRY]方法引用:
結果: [APPLE, BANANA, CHERRY]靜態方法引用:
原數據: [-3, -1, 0, 2, 5]
絕對值: [3, 1, 0, 2, 5]
對象轉換:實際業務應用
用戶信息轉換示例
讓我們看看在實際業務中如何使用map()
進行對象轉換:
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class ObjectTransformation {public static void main(String[] args) {System.out.println("=== 對象轉換應用 ===");List<User> users = Arrays.asList(new User("張三", 1995, "北京", "開發工程師"),new User("李四", 1988, "上海", "產品經理"),new User("王五", 1992, "廣州", "測試工程師"),new User("趙六", 1985, "深圳", "架構師"));// 1. 提取用戶名列表System.out.println("提取用戶名:");List<String> names = users.stream().map(User::getName) // 方法引用提取姓名.collect(Collectors.toList());System.out.println("用戶名: " + names);// 2. 計算用戶年齡int currentYear = LocalDate.now().getYear();System.out.println("\n計算用戶年齡:");List<Integer> ages = users.stream().map(user -> currentYear - user.getBirthYear()) // 計算年齡.collect(Collectors.toList());System.out.println("年齡: " + ages);// 3. 格式化用戶信息System.out.println("\n格式化用戶信息:");List<String> userInfos = users.stream().map(user -> String.format("%s(%d歲) - %s", user.getName(), currentYear - user.getBirthYear(),user.getPosition())).collect(Collectors.toList());userInfos.forEach(System.out::println);// 4. 轉換為用戶摘要對象System.out.println("\n轉換為用戶摘要:");List<UserSummary> summaries = users.stream().map(user -> new UserSummary(user.getName(),currentYear - user.getBirthYear(),user.getCity().length() > 2 ? user.getCity().substring(0, 2) : user.getCity())).collect(Collectors.toList());summaries.forEach(System.out::println);}
}class User {private String name;private int birthYear;private String city;private String position;public User(String name, int birthYear, String city, String position) {this.name = name;this.birthYear = birthYear;this.city = city;this.position = position;}// getter方法public String getName() { return name; }public int getBirthYear() { return birthYear; }public String getCity() { return city; }public String getPosition() { return position; }
}class UserSummary {private String name;private int age;private String cityPrefix;public UserSummary(String name, int age, String cityPrefix) {this.name = name;this.age = age;this.cityPrefix = cityPrefix;}@Overridepublic String toString() {return String.format("摘要[%s, %d歲, %s地區]", name, age, cityPrefix);}
}
輸出結果:
=== 對象轉換應用 ===
提取用戶名:
用戶名: [張三, 李四, 王五, 趙六]計算用戶年齡:
年齡: [30, 37, 33, 40]格式化用戶信息:
張三(30歲) - 開發工程師
李四(37歲) - 產品經理
王五(33歲) - 測試工程師
趙六(40歲) - 架構師轉換為用戶摘要:
摘要[張三, 30歲, 北京地區]
摘要[李四, 37歲, 上海地區]
摘要[王五, 33歲, 廣州地區]
摘要[趙六, 40歲, 深圳地區]
特殊類型的map():mapToInt、mapToLong、mapToDouble
當轉換結果是基本數據類型時,使用專門的map方法可以避免裝箱拆箱,提升性能:
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;public class PrimitiveMap {public static void main(String[] args) {System.out.println("=== 基本類型map()優化 ===");List<String> words = Arrays.asList("Java", "Python", "JavaScript", "Go");// 1. mapToInt - 轉換為IntStreamSystem.out.println("字符串長度統計:");IntStream lengths = words.stream().mapToInt(String::length); // 直接返回IntStream,避免裝箱System.out.println("各單詞長度: " + Arrays.toString(lengths.toArray()));// IntStream提供更多數學操作int totalLength = words.stream().mapToInt(String::length).sum(); // IntStream提供的sum()方法System.out.println("總長度: " + totalLength);double avgLength = words.stream().mapToInt(String::length).average() // 計算平均值.orElse(0);System.out.printf("平均長度: %.1f\n", avgLength);// 2. mapToDouble - 處理價格計算List<Product> products = Arrays.asList(new Product("手機", 2999),new Product("電腦", 8999),new Product("耳機", 299));System.out.println("\n價格計算:");double totalPrice = products.stream().mapToDouble(Product::getPrice) // 轉為DoubleStream.sum();System.out.printf("總價: %.2f元\n", totalPrice);// 計算含稅價格(稅率8%)System.out.println("含稅價格:");products.stream().mapToDouble(p -> p.getPrice() * 1.08).forEach(price -> System.out.printf("%.2f ", price));}
}class Product {private String name;private int price;public Product(String name, int price) {this.name = name;this.price = price;}public String getName() { return name; }public int getPrice() { return price; }
}
輸出結果:
=== 基本類型map()優化 ===
字符串長度統計:
各單詞長度: [4, 6, 10, 2]
總長度: 22
平均長度: 5.5價格計算:
總價: 12297.00元
含稅價格:
3238.92 9718.92 322.92
鏈式map():多重轉換
多個map()
可以鏈式調用,實現復雜的數據轉換管道:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class ChainedMap {public static void main(String[] args) {System.out.println("=== 鏈式map()轉換 ===");List<String> sentences = Arrays.asList("hello world", "java stream api", "functional programming");// 多重轉換:字符串 -> 大寫 -> 替換空格 -> 添加前綴System.out.println("多重轉換處理:");List<String> processed = sentences.stream().map(String::toUpperCase) // 第1步:轉大寫.map(s -> s.replace(" ", "_")) // 第2步:替換空格.map(s -> "PREFIX_" + s) // 第3步:添加前綴.collect(Collectors.toList());System.out.println("原始數據: " + sentences);System.out.println("處理結果: " + processed);// 數值處理鏈:原數 -> 平方 -> 加10 -> 轉字符串List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);System.out.println("\n數值處理鏈:");List<String> numberProcessed = numbers.stream().map(n -> n * n) // 平方.map(n -> n + 10) // 加10.map(n -> "result_" + n) // 轉字符串并加前綴.collect(Collectors.toList());System.out.println("原始數字: " + numbers);System.out.println("處理結果: " + numberProcessed);// 使用peek()調試中間步驟System.out.println("\n調試中間步驟:");numbers.stream().peek(n -> System.out.println("輸入: " + n)).map(n -> n * n).peek(n -> System.out.println("平方后: " + n)).map(n -> n + 10).peek(n -> System.out.println("加10后: " + n)).map(n -> "result_" + n).forEach(result -> System.out.println("最終結果: " + result));}
}
輸出結果:
=== 鏈式map()轉換 ===
多重轉換處理:
原始數據: [hello world, java stream api, functional programming]
處理結果: [PREFIX_HELLO_WORLD, PREFIX_JAVA_STREAM_API, PREFIX_FUNCTIONAL_PROGRAMMING]數值處理鏈:
原始數字: [1, 2, 3, 4, 5]
處理結果: [result_11, result_14, result_19, result_26, result_35]調試中間步驟:
輸入: 1
平方后: 1
加10后: 11
最終結果: result_11
輸入: 2
平方后: 4
加10后: 14
最終結果: result_14
輸入: 3
平方后: 9
加10后: 19
最終結果: result_19
輸入: 4
平方后: 16
加10后: 26
最終結果: result_26
輸入: 5
平方后: 25
加10后: 35
最終結果: result_35
map()與filter()組合:數據處理管道
map()
經常與filter()
組合使用,構成強大的數據處理管道:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class MapFilterCombination {public static void main(String[] args) {System.out.println("=== map()與filter()組合 ===");List<Employee> employees = Arrays.asList(new Employee("張三", 28, 12000),new Employee("李四", 35, 15000),new Employee("王五", 23, 8000),new Employee("趙六", 31, 18000),new Employee("孫七", 26, 10000));// 需求:找出高薪員工(>12000)的姓名,并格式化顯示System.out.println("高薪員工名單:");List<String> highSalaryNames = employees.stream().filter(emp -> emp.getSalary() > 12000) // 先過濾.map(emp -> emp.getName() + "(" + emp.getSalary() + "元)") // 再轉換格式.collect(Collectors.toList());highSalaryNames.forEach(System.out::println);// 需求:30歲以下員工的平均工資System.out.println("\n30歲以下員工的平均工資:");double avgSalary = employees.stream().filter(emp -> emp.getAge() < 30) // 篩選30歲以下.mapToInt(Employee::getSalary) // 提取工資.average() // 計算平均值.orElse(0);System.out.printf("平均工資: %.2f元\n", avgSalary);// 順序很重要!先過濾再轉換vs先轉換再過濾System.out.println("\n順序優化對比:");// ? 效率更高:先過濾,減少map()操作次數long count1 = employees.stream().filter(emp -> emp.getAge() > 25) // 先過濾.map(emp -> emp.getName().toUpperCase()) // 再轉換(只處理篩選后的數據).count();// ? 效率較低:先轉換,對所有數據進行map()操作long count2 = employees.stream().map(emp -> emp.getName().toUpperCase()) // 先轉換(處理所有數據).filter(name -> name.length() > 2) // 再過濾.count();System.out.println("優化后處理的數據量: " + count1);System.out.println("未優化處理的數據量: " + count2);}
}class Employee {private String name;private int age;private int salary;public Employee(String name, int age, int salary) {this.name = name;this.age = age;this.salary = salary;}public String getName() { return name; }public int getAge() { return age; }public int getSalary() { return salary; }
}
輸出結果:
=== map()與filter()組合 ===
高薪員工名單:
李四(15000元)
趙六(18000元)30歲以下員工的平均工資:
平均工資: 10000.00元順序優化對比:
優化后處理的數據量: 4
未優化處理的數據量: 0
實戰案例:訂單數據處理系統
讓我們構建一個完整的訂單數據處理系統,展示map()
在實際業務中的應用:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class OrderProcessingSystem {public static void main(String[] args) {System.out.println("=== 訂單數據處理系統 ===");List<Order> orders = Arrays.asList(new Order("ORD001", "張三", 1299.99, LocalDate.of(2025, 8, 20)),new Order("ORD002", "李四", 899.50, LocalDate.of(2025, 8, 21)),new Order("ORD003", "王五", 1599.00, LocalDate.of(2025, 8, 22)),new Order("ORD004", "趙六", 599.99, LocalDate.of(2025, 8, 23)));OrderProcessor processor = new OrderProcessor();// 場景1:生成訂單摘要報告System.out.println("訂單摘要報告:");List<String> summaries = processor.generateOrderSummaries(orders);summaries.forEach(System.out::println);// 場景2:計算含稅總價System.out.println("\n含稅價格計算(稅率10%):");List<TaxedOrder> taxedOrders = processor.calculateTaxedOrders(orders, 0.10);taxedOrders.forEach(System.out::println);// 場景3:生成客戶賬單System.out.println("\n客戶賬單:");List<CustomerBill> bills = processor.generateCustomerBills(orders);bills.forEach(System.out::println);// 場景4:數據導出格式轉換System.out.println("\nCSV導出格式:");List<String> csvLines = processor.convertToCsvFormat(orders);csvLines.forEach(System.out::println);}
}class OrderProcessor {private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");// 生成訂單摘要public List<String> generateOrderSummaries(List<Order> orders) {return orders.stream().map(order -> String.format("訂單%s: %s購買,金額%.2f元,日期%s",order.getOrderId(),order.getCustomerName(),order.getAmount(),order.getOrderDate().format(dateFormatter))).collect(Collectors.toList());}// 計算含稅訂單public List<TaxedOrder> calculateTaxedOrders(List<Order> orders, double taxRate) {return orders.stream().map(order -> new TaxedOrder(order.getOrderId(),order.getCustomerName(),order.getAmount(),order.getAmount() * taxRate, // 稅額order.getAmount() * (1 + taxRate) // 含稅總額)).collect(Collectors.toList());}// 生成客戶賬單public List<CustomerBill> generateCustomerBills(List<Order> orders) {return orders.stream().map(order -> new CustomerBill(order.getCustomerName(),order.getAmount(),calculatePoints(order.getAmount()), // 積分計算order.getOrderDate())).collect(Collectors.toList());}// 轉換為CSV格式public List<String> convertToCsvFormat(List<Order> orders) {return orders.stream().map(order -> String.join(",",order.getOrderId(),order.getCustomerName(),String.valueOf(order.getAmount()),order.getOrderDate().toString())).collect(Collectors.toList());}private int calculatePoints(double amount) {return (int) (amount / 10); // 每10元1積分}
}class Order {private String orderId;private String customerName;private double amount;private LocalDate orderDate;public Order(String orderId, String customerName, double amount, LocalDate orderDate) {this.orderId = orderId;this.customerName = customerName;this.amount = amount;this.orderDate = orderDate;}// getter方法public String getOrderId() { return orderId; }public String getCustomerName() { return customerName; }public double getAmount() { return amount; }public LocalDate getOrderDate() { return orderDate; }
}class TaxedOrder {private String orderId;private String customerName;private double originalAmount;private double taxAmount;private double totalAmount;public TaxedOrder(String orderId, String customerName, double originalAmount, double taxAmount, double totalAmount) {this.orderId = orderId;this.customerName = customerName;this.originalAmount = originalAmount;this.taxAmount = taxAmount;this.totalAmount = totalAmount;}@Overridepublic String toString() {return String.format("%s: %s,原價%.2f,稅額%.2f,總計%.2f",orderId, customerName, originalAmount, taxAmount, totalAmount);}
}class CustomerBill {private String customerName;private double amount;private int points;private LocalDate billDate;public CustomerBill(String customerName, double amount, int points, LocalDate billDate) {this.customerName = customerName;this.amount = amount;this.points = points;this.billDate = billDate;}@Overridepublic String toString() {return String.format("客戶%s: 消費%.2f元,獲得%d積分,日期%s",customerName, amount, points, billDate);}
}
輸出結果:
=== 訂單數據處理系統 ===
訂單摘要報告:
訂單ORD001: 張三購買,金額1299.99元,日期2025-08-20
訂單ORD002: 李四購買,金額899.50元,日期2025-08-21
訂單ORD003: 王五購買,金額1599.00元,日期2025-08-22
訂單ORD004: 趙六購買,金額599.99元,日期2025-08-23含稅價格計算(稅率10%):
ORD001: 張三,原價1299.99,稅額130.00,總計1429.99
ORD002: 李四,原價899.50,稅額89.95,總計989.45
ORD003: 王五,原價1599.00,稅額159.90,總計1758.90
ORD004: 趙六,原價599.99,稅額60.00,總計659.99客戶賬單:
客戶張三: 消費1299.99元,獲得129積分,日期2025-08-20
客戶李四: 消費899.50元,獲得89積分,日期2025-08-21
客戶王五: 消費1599.00元,獲得159積分,日期2025-08-22
客戶趙六: 消費599.99元,獲得59積分,日期2025-08-23CSV導出格式:
ORD001,張三,1299.99,2025-08-20
ORD002,李四,899.5,2025-08-21
ORD003,王五,1599.0,2025-08-22
ORD004,趙六,599.99,2025-08-23
本章小結
今天我們深入學習了map()
方法的強大功能:
核心概念:
- 一對一轉換:map()不改變元素數量,只改變元素的類型或值
- 函數式轉換:通過傳入轉換函數,實現靈活的數據變換
- 鏈式調用:多個map()可以串聯,構建數據轉換管道
重要方法:
- map():通用轉換方法
- mapToInt/mapToLong/mapToDouble:轉換為基本類型流,性能更好
- 方法引用:簡化常見的轉換操作
實用技巧:
- 與filter()組合:先過濾再轉換,提升性能
- 鏈式轉換:多步驟數據處理管道
- peek()調試:查看中間轉換步驟
性能考慮:
- 合理安排操作順序:先filter()后map()
- 使用mapToInt等避免裝箱拆箱
- 避免在map()中進行重復計算
實際應用場景:
- 數據格式轉換(對象轉字符串、類型轉換)
- 業務計算(價格計算、積分計算)
- 數據提取(從復雜對象中提取字段)
- 報表生成(格式化輸出)
下一章我們將學習《flatMap():處理嵌套數據的利器》,探索如何優雅地處理復雜的嵌套數據結構!
源代碼地址: https://github.com/qianmoQ/tutorial/tree/main/java8-stream-tutorial/src/main/java/org/devlive/tutorial/stream/chapter06