Java?Stream流常用方法歸納整理及案例解析
一、引言
1.1 什么是Java Stream
Java Stream API 是 Java 8 引入的一個(gè)重要特性,它提供了一種聲明式的方式來處理集合數(shù)據(jù)。Stream(流)是對集合(Collection)對象功能的增強(qiáng),專注于對集合對象進(jìn)行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數(shù)據(jù)操作(bulk data operation)。
核心特點(diǎn):
- 聲明式編程:使用類似 SQL 查詢的語法風(fēng)格表達(dá)數(shù)據(jù)處理邏輯
- 可組合性:可以將多個(gè)操作串聯(lián)起來形成流水線
- 可并行化:支持并行處理,充分利用多核 CPU 的優(yōu)勢
- 惰性求值:中間操作是惰性的,只有在終端操作時(shí)才會執(zhí)行
- 內(nèi)部迭代:由 Stream API 內(nèi)部處理迭代邏輯,代碼更簡潔
1.2 Stream的優(yōu)勢
| 特性 | 傳統(tǒng)方式 | Stream方式 |
|---|---|---|
| 代碼簡潔度 | 需要大量循環(huán)和條件判斷 | 鏈?zhǔn)秸{(diào)用,代碼更簡潔 |
| 可讀性 | 需要深入代碼邏輯才能理解 | 聲明式描述,語義清晰 |
| 并行處理 | 需要手動(dòng)編寫線程代碼 | 簡單調(diào)用 parallel() 即可 |
| 性能優(yōu)化 | 需要手動(dòng)優(yōu)化 | 底層自動(dòng)優(yōu)化 |
1.3 應(yīng)用場景
- 數(shù)據(jù)過濾與轉(zhuǎn)換:從集合中篩選、轉(zhuǎn)換數(shù)據(jù)
- 統(tǒng)計(jì)聚合:計(jì)算總和、平均值、最大值、最小值等
- 分組與分區(qū):將數(shù)據(jù)按條件分組或分區(qū)
- 數(shù)據(jù)轉(zhuǎn)換:將一種數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為另一種
- 復(fù)雜查詢:類似數(shù)據(jù)庫查詢的數(shù)據(jù)處理
二、Stream創(chuàng)建方法
2.1 從Collection創(chuàng)建
// 從List創(chuàng)建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> streamFromList = list.stream();
// 從Set創(chuàng)建
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
Stream<Integer> streamFromSet = set.stream();
// 并行流
Stream<String> parallelStream = list.parallelStream();方法簽名:
default Stream<E> stream() default Stream<E> parallelStream()
2.2 從數(shù)組創(chuàng)建
// 從數(shù)組創(chuàng)建
String[] array = {"Java", "Python", "Go"};
Stream<String> streamFromArray = Arrays.stream(array);
// 指定范圍創(chuàng)建流
Stream<String> partialStream = Arrays.stream(array, 0, 2);
// 使用Stream.of()
Stream<String> streamOf = Stream.of("Hello", "World", "Stream");
// 創(chuàng)建空流
Stream<String> emptyStream = Stream.empty();方法簽名:
public static <T> Stream<T> stream(T[] array) public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) public static<T> Stream<T> of(T... values) public static<T> Stream<T> empty()
2.3 從值創(chuàng)建
// 使用of()方法
Stream<String> valueStream = Stream.of("Java", "Stream", "API");
// 使用generate()生成無限流
Stream<Double> randomStream = Stream.generate(Math::random);
// 使用iterate()生成無限流
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2);方法簽名:
public static<T> Stream<T> generate(Supplier<T> s) public static<T> Stream<T> iterate(T seed, UnaryOperator<T> f)
2.4 其他創(chuàng)建方式
// 從文件創(chuàng)建
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// 使用Pattern分割字符串創(chuàng)建流
String text = "hello,world,java";
Stream<String> patternStream = Pattern.compile(",").splitAsStream(text);
// 使用IntStream、LongStream、DoubleStream等基本類型流
IntStream intStream = IntStream.range(1, 10);
LongStream longStream = LongStream.rangeClosed(1, 10);
DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);三、中間操作方法
中間操作返回一個(gè)新的Stream,可以串聯(lián)多個(gè)中間操作形成流水線。
3.1 篩選與切片
filter() - 過濾元素
方法簽名:
Stream<T> filter(Predicate<? super T> predicate)
功能說明: 根據(jù)給定的條件過濾流中的元素,保留滿足條件的元素。
參數(shù)解釋:
predicate:斷言函數(shù),用于測試每個(gè)元素是否滿足條件
返回值: 返回一個(gè)新的Stream,只包含滿足條件的元素
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 過濾偶數(shù)
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("偶數(shù): " + evenNumbers);
// 過濾長度大于3的字符串
List<String> words = Arrays.asList("Java", "Python", "Go", "JavaScript");
List<String> longWords = words.stream()
.filter(word -> word.length() > 3)
.collect(Collectors.toList());
System.out.println("長單詞: " + longWords);運(yùn)行結(jié)果:
偶數(shù): [2, 4, 6, 8, 10]
長單詞: [Java, Python, JavaScript]
distinct() - 去重
方法簽名:
Stream<T> distinct()
功能說明: 去除流中重復(fù)的元素(基于對象的equals()方法)
參數(shù)解釋: 無參數(shù)
返回值: 返回一個(gè)新的Stream,不包含重復(fù)元素
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("去重后: " + distinctNumbers);
// 對對象去重(需要重寫equals和hashCode)
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
List<Person> people = Arrays.asList(
new Person("張三", 25),
new Person("李四", 30),
new Person("張三", 25),
new Person("王五", 28)
);
List<Person> distinctPeople = people.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("去重后的人員: " + distinctPeople);運(yùn)行結(jié)果:
去重后: [1, 2, 3, 4, 5]
去重后的人員: [張三(25), 李四(30), 王五(28)]
limit() - 限制元素?cái)?shù)量
方法簽名:
Stream<T> limit(long maxSize)
功能說明: 截取流的前N個(gè)元素
參數(shù)解釋:
maxSize:要返回的元素?cái)?shù)量
返回值: 返回一個(gè)新的Stream,最多包含maxSize個(gè)元素
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 獲取前5個(gè)元素
List<Integer> firstFive = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println("前5個(gè)元素: " + firstFive);
// 先過濾再限制
List<Integer> firstThreeEven = numbers.stream()
.filter(n -> n % 2 == 0)
.limit(3)
.collect(Collectors.toList());
System.out.println("前3個(gè)偶數(shù): " + firstThreeEven);運(yùn)行結(jié)果:
前5個(gè)元素: [1, 2, 3, 4, 5]
前3個(gè)偶數(shù): [2, 4, 6]
skip() - 跳過元素
方法簽名:
Stream<T> skip(long n)
功能說明: 跳過流的前N個(gè)元素
參數(shù)解釋:
n:要跳過的元素?cái)?shù)量
返回值: 返回一個(gè)新的Stream,丟棄前n個(gè)元素后的剩余元素
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 跳過前5個(gè)元素
List<Integer> afterFive = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println("跳過前5個(gè)后: " + afterFive);
// 獲取第4到第7個(gè)元素
List<Integer> middleElements = numbers.stream()
.skip(3)
.limit(4)
.collect(Collectors.toList());
System.out.println("第4到第7個(gè)元素: " + middleElements);運(yùn)行結(jié)果:
跳過前5個(gè)后: [6, 7, 8, 9, 10]
第4到第7個(gè)元素: [4, 5, 6, 7]
3.2 映射
map() - 元素轉(zhuǎn)換
方法簽名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
功能說明: 將流中的每個(gè)元素轉(zhuǎn)換為另一種形式
參數(shù)解釋:
mapper:轉(zhuǎn)換函數(shù),接收一個(gè)元素,返回轉(zhuǎn)換后的元素
返回值: 返回一個(gè)新的Stream,包含轉(zhuǎn)換后的元素
代碼示例:
// 將字符串轉(zhuǎn)換為大寫
List<String> words = Arrays.asList("java", "python", "go");
List<String> upperWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("大寫字母: " + upperWords);
// 將對象提取屬性
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
}
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println("員工姓名: " + names);
// 計(jì)算平方
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("平方數(shù): " + squares);運(yùn)行結(jié)果:
大寫字母: [JAVA, PYTHON, GO]
員工姓名: [張三, 李四, 王五]
平方數(shù): [1, 4, 9, 16, 25]
flatMap() - 扁平化映射
方法簽名:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
功能說明: 將流中的每個(gè)元素轉(zhuǎn)換為一個(gè)流,然后將所有流連接成一個(gè)流
參數(shù)解釋:
mapper:轉(zhuǎn)換函數(shù),接收一個(gè)元素,返回一個(gè)Stream
返回值: 返回一個(gè)新的Stream,包含所有子流的元素
代碼示例:
// 將嵌套集合展平
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d", "e"),
Arrays.asList("f")
);
List<String> flattened = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("展平后: " + flattened);
// 將字符串的字符拆分成流
List<String> words = Arrays.asList("Hello", "World");
List<String> characters = words.stream()
.flatMap(word -> word.chars().mapToObj(c -> String.valueOf((char) c)))
.collect(Collectors.toList());
System.out.println("字符列表: " + characters);
// 獲取所有員工的所有技能
class Employee {
String name;
List<String> skills;
Employee(String name, List<String> skills) {
this.name = name;
this.skills = skills;
}
public List<String> getSkills() { return skills; }
}
List<Employee> employees = Arrays.asList(
new Employee("張三", Arrays.asList("Java", "Python")),
new Employee("李四", Arrays.asList("Go", "Rust", "C++")),
new Employee("王五", Arrays.asList("JavaScript"))
);
List<String> allSkills = employees.stream()
.flatMap(emp -> emp.getSkills().stream())
.distinct()
.collect(Collectors.toList());
System.out.println("所有技能: " + allSkills);運(yùn)行結(jié)果:
展平后: [a, b, c, d, e, f]
字符列表: [H, e, l, l, o, W, o, r, l, d]
所有技能: [Java, Python, Go, Rust, C++, JavaScript]
3.3 排序
sorted() - 自然排序
方法簽名:
Stream<T> sorted()
功能說明: 按自然順序?qū)α髦械脑剡M(jìn)行排序
參數(shù)解釋: 無參數(shù)
返回值: 返回一個(gè)新的Stream,元素已按自然順序排序
代碼示例:
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("升序排序: " + sortedNumbers);
List<String> words = Arrays.asList("Java", "Python", "Go", "C");
List<String> sortedWords = words.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("字母排序: " + sortedWords);運(yùn)行結(jié)果:
升序排序: [1, 2, 3, 5, 8, 9]
字母排序: [C, Go, Java, Python]
sorted(Comparator) - 自定義排序
方法簽名:
Stream<T> sorted(Comparator<? super T> comparator)
功能說明: 根據(jù)給定的比較器對流中的元素進(jìn)行排序
參數(shù)解釋:
comparator:比較器,用于定義排序規(guī)則
返回值: 返回一個(gè)新的Stream,元素已按指定規(guī)則排序
代碼示例:
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
// 按薪資升序排序
List<Employee> bySalaryAsc = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary))
.collect(Collectors.toList());
System.out.println("按薪資升序: " + bySalaryAsc);
// 按薪資降序排序
List<Employee> bySalaryDesc = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.collect(Collectors.toList());
System.out.println("按薪資降序: " + bySalaryDesc);
// 多條件排序:先按薪資,再按姓名
List<Employee> multiSort = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary)
.thenComparing(Employee::getName))
.collect(Collectors.toList());
System.out.println("多條件排序: " + multiSort);運(yùn)行結(jié)果:
按薪資升序: [張三(5000), 王五(6000), 李四(8000)]
按薪資降序: [李四(8000), 王五(6000), 張三(5000)]
多條件排序: [張三(5000), 王五(6000), 李四(8000)]
3.4 其他中間操作
peek() - 查看元素
方法簽名:
Stream<T> peek(Consumer<? super T> action)
功能說明: 對流中的每個(gè)元素執(zhí)行指定操作,主要用于調(diào)試
參數(shù)解釋:
action:要對每個(gè)元素執(zhí)行的操作
返回值: 返回一個(gè)新的Stream,元素不變,但每個(gè)元素都會執(zhí)行action
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用peek進(jìn)行調(diào)試
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("處理前: " + n))
.map(n -> n * n)
.peek(n -> System.out.println("處理后: " + n))
.collect(Collectors.toList());
System.out.println("最終結(jié)果: " + result);運(yùn)行結(jié)果:
處理前: 1
處理后: 1
處理前: 2
處理后: 4
處理前: 3
處理后: 9
處理前: 4
處理后: 16
處理前: 5
處理后: 25
最終結(jié)果: [1, 4, 9, 16, 25]
四、終端操作方法
終端操作會觸發(fā)流的執(zhí)行,并返回結(jié)果或產(chǎn)生副作用。
4.1 查找與匹配
allMatch() - 全部匹配
方法簽名:
boolean allMatch(Predicate<? super T> predicate)
功能說明: 檢查流中的所有元素是否都滿足指定條件
參數(shù)解釋:
predicate:斷言函數(shù),用于測試每個(gè)元素
返回值: 如果所有元素都滿足條件,返回true;否則返回false
代碼示例:
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0);
System.out.println("全部是偶數(shù): " + allEven);
List<Integer> numbers2 = Arrays.asList(2, 4, 6, 8, 9);
boolean allEven2 = numbers2.stream()
.allMatch(n -> n % 2 == 0);
System.out.println("全部是偶數(shù): " + allEven2);運(yùn)行結(jié)果:
全部是偶數(shù): true
全部是偶數(shù): false
anyMatch() - 任意匹配
方法簽名:
boolean anyMatch(Predicate<? super T> predicate)
功能說明: 檢查流中是否有任意元素滿足指定條件
參數(shù)解釋:
predicate:斷言函數(shù),用于測試每個(gè)元素
返回值: 如果至少有一個(gè)元素滿足條件,返回true;否則返回false
代碼示例:
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 8);
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0);
System.out.println("存在偶數(shù): " + hasEven);
List<String> words = Arrays.asList("Java", "Python", "Go");
boolean hasJava = words.stream()
.anyMatch(word -> word.equals("Java"));
System.out.println("包含Java: " + hasJava);運(yùn)行結(jié)果:
存在偶數(shù): true
包含Java: true
noneMatch() - 無匹配
方法簽名:
boolean noneMatch(Predicate<? super T> predicate)
功能說明: 檢查流中是否沒有元素滿足指定條件
參數(shù)解釋:
predicate:斷言函數(shù),用于測試每個(gè)元素
返回值: 如果沒有元素滿足條件,返回true;否則返回false
代碼示例:
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean noEven = numbers.stream()
.noneMatch(n -> n % 2 == 0);
System.out.println("沒有偶數(shù): " + noEven);
List<String> words = Arrays.asList("Java", "Python", "Go");
boolean noC = words.stream()
.noneMatch(word -> word.startsWith("C"));
System.out.println("沒有C開頭的單詞: " + noC);運(yùn)行結(jié)果:
沒有偶數(shù): true
沒有C開頭的單詞: true
findFirst() - 查找第一個(gè)
方法簽名:
Optional<T> findFirst()
功能說明: 返回流中的第一個(gè)元素
參數(shù)解釋: 無參數(shù)
返回值: 返回一個(gè)Optional,包含第一個(gè)元素;如果流為空,返回空的Optional
代碼示例:
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
Optional<Integer> first = numbers.stream()
.findFirst();
System.out.println("第一個(gè)元素: " + first.orElse(-1));
List<String> words = Arrays.asList("Java", "Python", "Go");
Optional<String> firstLong = words.stream()
.filter(word -> word.length() > 3)
.findFirst();
System.out.println("第一個(gè)長單詞: " + firstLong.orElse("無"));運(yùn)行結(jié)果:
第一個(gè)元素: 3
第一個(gè)長單詞: Java
findAny() - 查找任意一個(gè)
方法簽名:
Optional<T> findAny()
功能說明: 返回流中的任意一個(gè)元素
參數(shù)解釋: 無參數(shù)
返回值: 返回一個(gè)Optional,包含任意一個(gè)元素;如果流為空,返回空的Optional
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> any = numbers.stream()
.findAny();
System.out.println("任意元素: " + any.orElse(-1));
// 在并行流中,findAny可能返回不同的元素
List<Integer> parallelResult = numbers.parallelStream()
.filter(n -> n > 2)
.findAny();
System.out.println("并行流中大于2的任意元素: " + parallelResult.orElse(-1));運(yùn)行結(jié)果:
任意元素: 1
并行流中大于2的任意元素: 3(或4、5)
4.2 歸約
reduce() - 歸約操作
方法簽名:
Optional<T> reduce(BinaryOperator<T> accumulator) T reduce(T identity, BinaryOperator<T> accumulator) <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
功能說明: 將流中的元素組合成一個(gè)結(jié)果
參數(shù)解釋:
identity:初始值accumulator:累加器函數(shù),定義如何組合元素combiner:合并器函數(shù),用于并行流時(shí)合并部分結(jié)果
返回值: 返回歸約后的結(jié)果
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 無初始值,返回Optional
Optional<Integer> sum1 = numbers.stream()
.reduce((a, b) -> a + b);
System.out.println("求和(無初始值): " + sum1.orElse(0));
// 有初始值
int sum2 = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("求和(有初始值): " + sum2);
// 使用方法引用
int sum3 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("求和(方法引用): " + sum3);
// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
System.out.println("最大值: " + max.orElse(0));
// 求最小值
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
System.out.println("最小值: " + min.orElse(0));
// 求乘積
int product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("乘積: " + product);
// 字符串連接
List<String> words = Arrays.asList("Java", "Stream", "API");
String joined = words.stream()
.reduce("", (a, b) -> a + b);
System.out.println("字符串連接: " + joined);
String joinedWithComma = words.stream()
.reduce((a, b) -> a + "," + b)
.orElse("");
System.out.println("用逗號連接: " + joinedWithComma);運(yùn)行結(jié)果:
求和(無初始值): 15
求和(有初始值): 15
求和(方法引用): 15
最大值: 5
最小值: 1
乘積: 120
字符串連接: JavaStreamAPI
用逗號連接: Java,Stream,API
4.3 收集
collect() - 收集結(jié)果
方法簽名:
<R> R collect(Collector<? super T, A, R> collector) <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
功能說明: 將流中的元素收集到容器中
參數(shù)解釋:
collector:收集器,定義如何收集元素supplier:提供結(jié)果容器accumulator:累加器,將元素添加到容器combiner:合并器,用于并行流時(shí)合并部分結(jié)果
返回值: 返回收集后的結(jié)果
Collectors常用方法
toList() - 收集到List
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("偶數(shù)列表: " + evenNumbers);
運(yùn)行結(jié)果:
偶數(shù)列表: [2, 4]
toSet() - 收集到Set
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
Set<Integer> uniqueNumbers = numbers.stream()
.collect(Collectors.toSet());
System.out.println("唯一數(shù)字: " + uniqueNumbers);
運(yùn)行結(jié)果:
唯一數(shù)字: [1, 2, 3, 4]
toCollection() - 收集到指定集合
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
LinkedList<Integer> linkedList = numbers.stream()
.sorted()
.collect(Collectors.toCollection(LinkedList::new));
System.out.println("有序鏈表: " + linkedList);
運(yùn)行結(jié)果:
有序鏈表: [1, 2, 5, 8, 9]
toMap() - 收集到Map
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
// 轉(zhuǎn)換為Map:姓名->員工對象
Map<String, Employee> nameToEmployee = employees.stream()
.collect(Collectors.toMap(Employee::getName, e -> e));
System.out.println("姓名到員工: " + nameToEmployee);
// 轉(zhuǎn)換為Map:姓名->薪資
Map<String, Integer> nameToSalary = employees.stream()
.collect(Collectors.toMap(Employee::getName, Employee::getSalary));
System.out.println("姓名到薪資: " + nameToSalary);
// 處理鍵沖突(使用新值)
List<Employee> employeesWithConflict = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("張三", 7000) // 同名員工
);
Map<String, Integer> handleConflict = employeesWithConflict.stream()
.collect(Collectors.toMap(
Employee::getName,
Employee::getSalary,
(oldValue, newValue) -> newValue
));
System.out.println("處理鍵沖突: " + handleConflict);運(yùn)行結(jié)果:
姓名到員工: {張三=張三(5000), 李四=李四(8000), 王五=王五(6000)}
姓名到薪資: {張三=5000, 李四=8000, 王五=6000}
處理鍵沖突: {張三=7000, 李四=8000}
joining() - 字符串連接
List<String> words = Arrays.asList("Java", "Stream", "API");
// 直接連接
String joined1 = words.stream()
.collect(Collectors.joining());
System.out.println("直接連接: " + joined1);
// 用逗號分隔
String joined2 = words.stream()
.collect(Collectors.joining(", "));
System.out.println("逗號分隔: " + joined2);
// 用分隔符、前綴、后綴
String joined3 = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("帶前后綴: " + joined3);運(yùn)行結(jié)果:
直接連接: JavaStreamAPI
逗號分隔: Java, Stream, API
帶前后綴: [Java, Stream, API]
groupingBy() - 分組
class Employee {
String name;
String department;
int salary;
Employee(String name, String department, int salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("張三", "技術(shù)部", 5000),
new Employee("李四", "技術(shù)部", 8000),
new Employee("王五", "市場部", 6000),
new Employee("趙六", "市場部", 7000),
new Employee("孫七", "財(cái)務(wù)部", 5500)
);
// 按部門分組
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("按部門分組: " + byDept);
// 分組后計(jì)數(shù)
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()));
System.out.println("各部門人數(shù): " + countByDept);
// 分組后求平均值
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingInt(Employee::getSalary)
));
System.out.println("各部門平均薪資: " + avgSalaryByDept);
// 多級分組:先按部門,再按薪資范圍
Map<String, Map<String, List<Employee>>> multiGroup = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(e -> e.getSalary() > 6000 ? "高薪" : "普通")
));
System.out.println("多級分組: " + multiGroup);運(yùn)行結(jié)果:
按部門分組: {技術(shù)部=[張三(5000), 李四(8000)], 市場部=[王五(6000), 趙六(7000)], 財(cái)務(wù)部=[孫七(5500)]}
各部門人數(shù): {技術(shù)部=2, 市場部=2, 財(cái)務(wù)部=1}
各部門平均薪資: {技術(shù)部=6500.0, 市場部=6500.0, 財(cái)務(wù)部=5500.0}
多級分組: {技術(shù)部={普通=[張三(5000)], 高薪=[李四(8000)]}, 市場部={普通=[王五(6000)], 高薪=[趙六(7000)]}, 財(cái)務(wù)部={普通=[孫七(5500)]}}
partitioningBy() - 分區(qū)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 按是否為偶數(shù)分區(qū)
Map<Boolean, List<Integer>> partitionByEven = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println("按奇偶分區(qū): " + partitionByEven);
// 分區(qū)后計(jì)數(shù)
Map<Boolean, Long> countPartition = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));
System.out.println("奇偶數(shù)量: " + countPartition);
// 按薪資是否大于6000分區(qū)
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000),
new Employee("趙六", 7000)
);
Map<Boolean, List<Employee>> partitionBySalary = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
System.out.println("按薪資分區(qū): " + partitionBySalary);運(yùn)行結(jié)果:
按奇偶分區(qū): {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
奇偶數(shù)量: {false=5, true=5}
按薪資分區(qū): {false=[張三(5000), 王五(6000)], true=[李四(8000), 趙六(7000)]}
mapping() - 映射后收集
List<String> words = Arrays.asList("java", "stream", "api", "lambda");
// 轉(zhuǎn)換為大寫后收集
List<String> upperWords = words.stream()
.collect(Collectors.mapping(String::toUpperCase, Collectors.toList()));
System.out.println("大寫單詞: " + upperWords);
// 分組后映射
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.mapping(String::toUpperCase, Collectors.toList())
));
System.out.println("按長度分組并轉(zhuǎn)大寫: " + byLength);運(yùn)行結(jié)果:
大寫單詞: [JAVA, STREAM, API, LAMBDA]
按長度分組并轉(zhuǎn)大寫: {4=[JAVA, API], 6=[STREAM, LAMBDA]}
4.4 統(tǒng)計(jì)
count() - 計(jì)數(shù)
方法簽名:
long count()
功能說明: 返回流中元素的數(shù)量
參數(shù)解釋: 無參數(shù)
返回值: 返回元素個(gè)數(shù)
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 總數(shù)
long total = numbers.stream().count();
System.out.println("總數(shù): " + total);
// 偶數(shù)個(gè)數(shù)
long evenCount = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
System.out.println("偶數(shù)個(gè)數(shù): " + evenCount);
List<String> words = Arrays.asList("Java", "Python", "Go", "JavaScript");
long longWordCount = words.stream()
.filter(word -> word.length() > 3)
.count();
System.out.println("長單詞個(gè)數(shù): " + longWordCount);運(yùn)行結(jié)果:
總數(shù): 10
偶數(shù)個(gè)數(shù): 5
長單詞個(gè)數(shù): 3
max() - 最大值
方法簽名:
Optional<T> max(Comparator<? super T> comparator)
功能說明: 根據(jù)比較器返回流中的最大元素
參數(shù)解釋:
comparator:比較器,用于比較元素大小
返回值: 返回Optional,包含最大元素;如果流為空,返回空的Optional
代碼示例:
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
Optional<Integer> maxNumber = numbers.stream()
.max(Comparator.naturalOrder());
System.out.println("最大數(shù)字: " + maxNumber.orElse(-1));
class Employee {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public int getSalary() { return salary; }
@Override
public String toString() {
return name + "(" + salary + ")";
}
}
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
Optional<Employee> highestPaid = employees.stream()
.max(Comparator.comparingInt(Employee::getSalary));
System.out.println("最高薪資員工: " + highestPaid);運(yùn)行結(jié)果:
最大數(shù)字: 9
最高薪資員工: 李四(8000)
min() - 最小值
方法簽名:
Optional<T> min(Comparator<? super T> comparator)
功能說明: 根據(jù)比較器返回流中的最小元素
參數(shù)解釋:
comparator:比較器,用于比較元素大小
返回值: 返回Optional,包含最小元素;如果流為空,返回空的Optional
代碼示例:
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
Optional<Integer> minNumber = numbers.stream()
.min(Comparator.naturalOrder());
System.out.println("最小數(shù)字: " + minNumber.orElse(-1));
List<String> words = Arrays.asList("Java", "Python", "Go", "C");
Optional<String> shortest = words.stream()
.min(Comparator.comparingInt(String::length));
System.out.println("最短單詞: " + shortest.orElse("無"));運(yùn)行結(jié)果:
最小數(shù)字: 1
最短單詞: C
average() - 平均值(IntStream/LongStream/DoubleStream)
方法簽名:
OptionalDouble average()
功能說明: 計(jì)算流中元素的平均值
參數(shù)解釋: 無參數(shù)
返回值: 返回OptionalDouble,包含平均值;如果流為空,返回空的OptionalDouble
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 計(jì)算平均值
OptionalDouble average = numbers.stream()
.mapToInt(Integer::intValue)
.average();
System.out.println("平均值: " + average.orElse(0));
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
OptionalDouble avgSalary = employees.stream()
.mapToInt(Employee::getSalary)
.average();
System.out.println("平均薪資: " + avgSalary.orElse(0));
// 使用Collectors.averagingInt
Double avgSalary2 = employees.stream()
.collect(Collectors.averagingInt(Employee::getSalary));
System.out.println("平均薪資(方式2): " + avgSalary2);運(yùn)行結(jié)果:
平均值: 3.0
平均薪資: 6333.333333333333
平均薪資(方式2): 6333.333333333333
sum() - 求和(IntStream/LongStream/DoubleStream)
方法簽名:
int sum() // IntStream long sum() // LongStream double sum() // DoubleStream
功能說明: 計(jì)算流中元素的總和
參數(shù)解釋: 無參數(shù)
返回值: 返回元素的和
代碼示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("求和: " + sum);
// 使用reduce求和
int sum2 = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("求和(reduce): " + sum2);
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
int totalSalary = employees.stream()
.mapToInt(Employee::getSalary)
.sum();
System.out.println("總薪資: " + totalSalary);運(yùn)行結(jié)果:
求和: 15
求和(reduce): 15
總薪資: 19000
summarizingInt/Long/Double - 綜合統(tǒng)計(jì)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("綜合統(tǒng)計(jì):");
System.out.println(" 個(gè)數(shù): " + stats.getCount());
System.out.println(" 總和: " + stats.getSum());
System.out.println(" 平均值: " + stats.getAverage());
System.out.println(" 最小值: " + stats.getMin());
System.out.println(" 最大值: " + stats.getMax());
List<Employee> employees = Arrays.asList(
new Employee("張三", 5000),
new Employee("李四", 8000),
new Employee("王五", 6000)
);
IntSummaryStatistics salaryStats = employees.stream()
.collect(Collectors.summarizingInt(Employee::getSalary));
System.out.println("\n薪資統(tǒng)計(jì):");
System.out.println(" 人數(shù): " + salaryStats.getCount());
System.out.println(" 總薪資: " + salaryStats.getSum());
System.out.println(" 平均薪資: " + salaryStats.getAverage());
System.out.println(" 最低薪資: " + salaryStats.getMin());
System.out.println(" 最高薪資: " + salaryStats.getMax());運(yùn)行結(jié)果:
綜合統(tǒng)計(jì):
個(gè)數(shù): 10
總和: 55
平均值: 5.5
最小值: 1
最大值: 10薪資統(tǒng)計(jì):
人數(shù): 3
總薪資: 19000
平均薪資: 6333.333333333333
最低薪資: 5000
最高薪資: 8000
五、實(shí)際應(yīng)用案例
案例一:電商訂單數(shù)據(jù)處理
場景描述: 處理訂單數(shù)據(jù),統(tǒng)計(jì)各商品類別的銷售情況。
import java.util.*;
import java.util.stream.Collectors;
class OrderItem {
String productId;
String productName;
String category;
double price;
int quantity;
OrderItem(String productId, String productName, String category, double price, int quantity) {
this.productId = productId;
this.productName = productName;
this.category = category;
this.price = price;
this.quantity = quantity;
}
public String getCategory() { return category; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public double getTotal() { return price * quantity; }
@Override
public String toString() {
return productName + "(" + category + ") - 數(shù)量:" + quantity + " 單價(jià):" + price;
}
}
class Order {
String orderId;
List<OrderItem> items;
Date orderDate;
Order(String orderId, List<OrderItem> items, Date orderDate) {
this.orderId = orderId;
this.items = items;
this.orderDate = orderDate;
}
public List<OrderItem> getItems() { return items; }
public Date getOrderDate() { return orderDate; }
}
public class EcommerceAnalysis {
public static void main(String[] args) {
// 創(chuàng)建訂單數(shù)據(jù)
List<Order> orders = Arrays.asList(
new Order("ORD001", Arrays.asList(
new OrderItem("P001", "手機(jī)", "電子產(chǎn)品", 3999.0, 1),
new OrderItem("P002", "耳機(jī)", "電子產(chǎn)品", 299.0, 2)
), new Date()),
new Order("ORD002", Arrays.asList(
new OrderItem("P003", "T恤", "服裝", 199.0, 3),
new OrderItem("P004", "牛仔褲", "服裝", 299.0, 2)
), new Date()),
new Order("ORD003", Arrays.asList(
new OrderItem("P001", "手機(jī)", "電子產(chǎn)品", 3999.0, 2),
new OrderItem("P005", "運(yùn)動(dòng)鞋", "鞋類", 599.0, 1)
), new Date())
);
// 1. 統(tǒng)計(jì)各類別的銷售額
Map<String, Double> salesByCategory = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.summingDouble(OrderItem::getTotal)
));
System.out.println("各類別銷售額: " + salesByCategory);
// 2. 找出銷量最高的商品
Optional<OrderItem> bestSellingItem = orders.stream()
.flatMap(order -> order.getItems().stream())
.max(Comparator.comparingInt(OrderItem::getQuantity));
System.out.println("\n銷量最高商品: " + bestSellingItem);
// 3. 計(jì)算訂單總金額
double totalRevenue = orders.stream()
.flatMap(order -> order.getItems().stream())
.mapToDouble(OrderItem::getTotal)
.sum();
System.out.println("\n總銷售額: " + totalRevenue);
// 4. 按類別分組,統(tǒng)計(jì)每個(gè)類別的商品數(shù)量和平均價(jià)格
Map<String, Map<String, Object>> categoryStats = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.collectingAndThen(
Collectors.toList(),
items -> {
Map<String, Object> stats = new HashMap<>();
stats.put("商品數(shù)量", items.size());
stats.put("平均單價(jià)", items.stream().collect(Collectors.averagingDouble(OrderItem::getPrice)));
stats.put("總銷量", items.stream().mapToInt(OrderItem::getQuantity).sum());
return stats;
}
)
));
System.out.println("\n類別統(tǒng)計(jì): " + categoryStats);
// 5. 篩選出電子產(chǎn)品訂單,并按價(jià)格排序
List<OrderItem> electronicsSorted = orders.stream()
.flatMap(order -> order.getItems().stream())
.filter(item -> item.getCategory().equals("電子產(chǎn)品"))
.sorted(Comparator.comparingDouble(OrderItem::getPrice).reversed())
.collect(Collectors.toList());
System.out.println("\n電子產(chǎn)品(按價(jià)格降序): " + electronicsSorted);
}
}運(yùn)行結(jié)果:
各類別銷售額: {電子產(chǎn)品=9296.0, 服裝=1194.0, 鞋類=599.0}
銷量最高商品: 手機(jī)(電子產(chǎn)品) - 數(shù)量:2 單價(jià):3999.0
總銷售額: 11089.0
類別統(tǒng)計(jì): {
電子產(chǎn)品={商品數(shù)量=3, 平均單價(jià)=2765.6666666666665, 總銷量=5},
服裝={商品數(shù)量=2, 平均單價(jià)=249.0, 總銷量=5},
鞋類={商品數(shù)量=1, 平均單價(jià)=599.0, 總銷量=1}
}電子產(chǎn)品(按價(jià)格降序): [手機(jī)(電子產(chǎn)品) - 數(shù)量:1 單價(jià):3999.0, 手機(jī)(電子產(chǎn)品) - 數(shù)量:2 單價(jià):3999.0, 耳機(jī)(電子產(chǎn)品) - 數(shù)量:2 單價(jià):299.0]
案例二:學(xué)生成績管理系統(tǒng)
場景描述: 處理學(xué)生成績數(shù)據(jù),進(jìn)行成績統(tǒng)計(jì)和分析。
import java.util.*;
import java.util.stream.Collectors;
class Student {
String id;
String name;
String className;
Map<String, Integer> scores; // 科目 -> 分?jǐn)?shù)
Student(String id, String name, String className, Map<String, Integer> scores) {
this.id = id;
this.name = name;
this.className = className;
this.scores = scores;
}
public String getId() { return id; }
public String getName() { return name; }
public String getClassName() { return className; }
public Map<String, Integer> getScores() { return scores; }
public double getAverageScore() {
return scores.values().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0);
}
public int getTotalScore() {
return scores.values().stream()
.mapToInt(Integer::intValue)
.sum();
}
@Override
public String toString() {
return name + "(" + className + ") - 平均分:" + String.format("%.1f", getAverageScore());
}
}
public class StudentGradeAnalysis {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("S001", "張三", "一班",
Map.of("語文", 85, "數(shù)學(xué)", 90, "英語", 88, "物理", 92)),
new Student("S002", "李四", "一班",
Map.of("語文", 78, "數(shù)學(xué)", 85, "英語", 80, "物理", 75)),
new Student("S003", "王五", "二班",
Map.of("語文", 92, "數(shù)學(xué)", 88, "英語", 95, "物理", 90)),
new Student("S004", "趙六", "二班",
Map.of("語文", 65, "數(shù)學(xué)", 70, "英語", 68, "物理", 72)),
new Student("S005", "孫七", "一班",
Map.of("語文", 88, "數(shù)學(xué)", 95, "英語", 90, "物理", 93))
);
// 1. 計(jì)算全班各科平均分
Map<String, Double> subjectAvg = students.stream()
.flatMap(student -> student.getScores().entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.averagingInt(Map.Entry::getValue)
));
System.out.println("各科平均分: " + subjectAvg);
// 2. 找出總分最高的學(xué)生
Optional<Student> topStudent = students.stream()
.max(Comparator.comparingInt(Student::getTotalScore));
System.out.println("\n總分最高學(xué)生: " + topStudent);
// 3. 按班級分組,計(jì)算每個(gè)班級的平均分
Map<String, Double> classAvg = students.stream()
.collect(Collectors.groupingBy(
Student::getClassName,
Collectors.averagingDouble(Student::getAverageScore)
));
System.out.println("\n各班級平均分: " + classAvg);
// 4. 找出各科分?jǐn)?shù)最高的學(xué)生
Map<String, Student> topBySubject = students.stream()
.flatMap(student -> student.getScores().entrySet().stream()
.map(entry -> Map.entry(entry.getKey(),
Map.entry(student, entry.getValue()))))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.collectingAndThen(
Collectors.maxBy(
Comparator.comparingInt(e -> e.getValue().getValue())
),
opt -> opt.map(Map.Entry::getValue).map(Map.Entry::getKey).orElse(null)
)
));
System.out.println("\n各科最高分學(xué)生:");
topBySubject.forEach((subject, student) ->
System.out.println(" " + subject + ": " + student.getName() +
"(" + student.getScores().get(subject) + "分)"));
// 5. 統(tǒng)計(jì)分?jǐn)?shù)段分布(以數(shù)學(xué)為例)
String targetSubject = "數(shù)學(xué)";
Map<String, Long> gradeDistribution = students.stream()
.filter(s -> s.getScores().containsKey(targetSubject))
.collect(Collectors.groupingBy(
student -> {
int score = student.getScores().get(targetSubject);
if (score >= 90) return "優(yōu)秀";
else if (score >= 80) return "良好";
else if (score >= 70) return "中等";
else if (score >= 60) return "及格";
else return "不及格";
},
Collectors.counting()
));
System.out.println("\n" + targetSubject + "分?jǐn)?shù)段分布: " + gradeDistribution);
// 6. 找出所有科目都及格的學(xué)生
List<Student> allPass = students.stream()
.filter(student -> student.getScores().values().stream()
.allMatch(score -> score >= 60))
.collect(Collectors.toList());
System.out.println("\n所有科目及格的學(xué)生: " + allPass);
// 7. 按總分排名
List<Student> ranked = students.stream()
.sorted(Comparator.comparingInt(Student::getTotalScore).reversed())
.collect(Collectors.toList());
System.out.println("\n學(xué)生總分排名:");
for (int i = 0; i < ranked.size(); i++) {
System.out.printf("第%d名: %s - 總分: %d\n",
i + 1, ranked.get(i).getName(), ranked.get(i).getTotalScore());
}
}
}運(yùn)行結(jié)果:
各科平均分: {語文=81.6, 數(shù)學(xué)=85.6, 英語=84.2, 物理=86.4}
總分最高學(xué)生: 孫七(一班) - 平均分:91.5
各班級平均分: {一班=87.16666666666667, 二班=78.5}
各科最高分學(xué)生:
語文: 王五(92分)
數(shù)學(xué): 孫七(95分)
英語: 王五(95分)
物理: 孫七(93分)數(shù)學(xué)分?jǐn)?shù)段分布: {良好=3, 優(yōu)秀=2}
所有科目及格的學(xué)生: [張三(一班) - 平均分:88.8, 李四(一班) - 平均分:79.5, 王五(二班) - 平均分:91.2, 孫七(一班) - 平均分:91.5]
學(xué)生總分排名:
第1名: 孫七 - 總分:366
第2名: 王五 - 總分:365
第3名: 張三 - 總分:355
第4名: 李四 - 總分:318
第5名: 趙六 - 總分:275
案例三:文本分析與詞頻統(tǒng)計(jì)
場景描述: 分析文本內(nèi)容,統(tǒng)計(jì)詞頻、查找最長單詞等。
import java.util.*;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
public class TextAnalysis {
public static void main(String[] args) {
String text = "Java Stream API is a powerful tool for data processing. " +
"It provides a declarative approach to process collections. " +
"Stream operations can be chained together to form pipelines. " +
"The API supports both sequential and parallel processing.";
// 1. 分詞并統(tǒng)計(jì)詞頻
Map<String, Long> wordCount = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.filter(word -> word.length() > 2) // 過濾掉長度<=2的詞
.collect(Collectors.groupingBy(
word -> word,
Collectors.counting()
));
System.out.println("詞頻統(tǒng)計(jì):");
wordCount.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry -> System.out.printf(" %s: %d\n", entry.getKey(), entry.getValue()));
// 2. 找出最長的單詞
String longestWord = Pattern.compile("\\s+")
.splitAsStream(text.replaceAll("[^a-zA-Z\\s]", ""))
.max(Comparator.comparingInt(String::length))
.orElse("");
System.out.println("\n最長的單詞: " + longestWord + " (長度: " + longestWord.length() + ")");
// 3. 統(tǒng)計(jì)不同長度單詞的數(shù)量
Map<Integer, Long> lengthDistribution = Pattern.compile("\\s+")
.splitAsStream(text.replaceAll("[^a-zA-Z\\s]", ""))
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println("\n單詞長度分布:");
lengthDistribution.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> System.out.printf(" 長度%d: %d個(gè)\n", entry.getKey(), entry.getValue()));
// 4. 找出所有以特定字母開頭的單詞
char startLetter = 'p';
List<String> wordsStartingWith = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.filter(word -> word.startsWith(String.valueOf(startLetter)))
.distinct()
.collect(Collectors.toList());
System.out.println("\n以'" + startLetter + "'開頭的單詞: " + wordsStartingWith);
// 5. 檢查文本是否包含特定詞匯
List<String> targetWords = Arrays.asList("stream", "api", "data", "parallel");
Map<String, Boolean> wordPresence = targetWords.stream()
.collect(Collectors.toMap(
word -> word,
word -> text.toLowerCase().contains(word.toLowerCase())
));
System.out.println("\n關(guān)鍵詞檢查:");
wordPresence.forEach((word, present) ->
System.out.println(" " + word + ": " + (present ? "存在" : "不存在")));
// 6. 提取所有單詞并按字母順序排序
List<String> allWordsSorted = Pattern.compile("\\s+")
.splitAsStream(text.toLowerCase()
.replaceAll("[^a-zA-Z\\s]", ""))
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("\n所有單詞(排序后):");
System.out.println(" " + String.join(", ", allWordsSorted));
// 7. 計(jì)算文本的基本統(tǒng)計(jì)信息
String[] words = text.replaceAll("[^a-zA-Z\\s]", "").split("\\s+");
String[] sentences = text.split("[.!?]");
String[] characters = text.split("");
System.out.println("\n文本統(tǒng)計(jì):");
System.out.println(" 單詞數(shù): " + words.length);
System.out.println(" 句子數(shù): " + sentences.length);
System.out.println(" 字符數(shù): " + characters.length);
System.out.println(" 平均單詞長度: " +
Arrays.stream(words).mapToInt(String::length).average().orElse(0));
}
}運(yùn)行結(jié)果:
詞頻統(tǒng)計(jì):
stream: 3
a: 3
processing: 2
the: 2
api: 2
declarative: 1
provides: 1
approach: 1
process: 1
collections: 1最長的單詞: processing (長度: 10)
單詞長度分布:
長度1: 0個(gè)
長度2: 0個(gè)
長度3: 1個(gè)
長度4: 6個(gè)
長度5: 4個(gè)
長度6: 3個(gè)
長度7: 1個(gè)
長度8: 2個(gè)
長度10: 2個(gè)
長度11: 1個(gè)以'p'開頭的單詞: [processing, provides, process, pipelines, parallel]
關(guān)鍵詞檢查:
stream: 存在
api: 存在
data: 存在
parallel: 存在所有單詞(排序后):
a, and, api, approach, be, both, can, chains, collections, data, declarative, for, form, is, it, operations, pipelines, powerful, process, processing, provides, sequential, stream, supports, the, tool文本統(tǒng)計(jì):
單詞數(shù): 26
句子數(shù): 4
字符數(shù): 194
平均單詞長度: 5.846153846153846
案例四:員工信息管理
場景描述: 管理員工信息,進(jìn)行查詢、分組、統(tǒng)計(jì)分析。
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
class Employee {
String id;
String name;
String department;
String position;
int salary;
LocalDate hireDate;
int age;
Employee(String id, String name, String department, String position,
int salary, LocalDate hireDate, int age) {
this.id = id;
this.name = name;
this.department = department;
this.position = position;
this.salary = salary;
this.hireDate = hireDate;
this.age = age;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getDepartment() { return department; }
public String getPosition() { return position; }
public int getSalary() { return salary; }
public LocalDate getHireDate() { return hireDate; }
public int getAge() { return age; }
// 計(jì)算工齡(年)
public int getYearsOfService() {
return LocalDate.now().getYear() - hireDate.getYear();
}
@Override
public String toString() {
return String.format("%s(%s) - %s, 薪資:%d", name, department, position, salary);
}
}
public class EmployeeManagement {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("E001", "張三", "技術(shù)部", "高級工程師", 25000,
LocalDate.of(2018, 3, 15), 32),
new Employee("E002", "李四", "技術(shù)部", "中級工程師", 18000,
LocalDate.of(2020, 7, 1), 28),
new Employee("E003", "王五", "技術(shù)部", "初級工程師", 12000,
LocalDate.of(2022, 1, 10), 25),
new Employee("E004", "趙六", "市場部", "市場經(jīng)理", 22000,
LocalDate.of(2019, 5, 20), 35),
new Employee("E005", "孫七", "市場部", "銷售代表", 10000,
LocalDate.of(2021, 9, 1), 26),
new Employee("E006", "周八", "財(cái)務(wù)部", "財(cái)務(wù)經(jīng)理", 20000,
LocalDate.of(2017, 11, 15), 38),
new Employee("E007", "吳九", "技術(shù)部", "架構(gòu)師", 30000,
LocalDate.of(2016, 2, 1), 36),
new Employee("E008", "鄭十", "市場部", "市場專員", 9000,
LocalDate.of(2023, 3, 1), 24)
);
// 1. 按部門統(tǒng)計(jì)員工數(shù)量和平均薪資
Map<String, Map<String, Object>> deptStats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
empList -> {
Map<String, Object> stats = new HashMap<>();
stats.put("人數(shù)", empList.size());
stats.put("平均薪資", empList.stream()
.collect(Collectors.averagingInt(Employee::getSalary)));
stats.put("薪資總和", empList.stream()
.mapToInt(Employee::getSalary).sum());
return stats;
}
)
));
System.out.println("部門統(tǒng)計(jì):");
deptStats.forEach((dept, stats) ->
System.out.printf(" %s: 人數(shù)=%d, 平均薪資=%.0f, 總和=%d\n",
dept, stats.get("人數(shù)"), stats.get("平均薪資"), stats.get("薪資總和")));
// 2. 找出薪資前3的員工
List<Employee> topSalaries = employees.stream()
.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.limit(3)
.collect(Collectors.toList());
System.out.println("\n薪資前3的員工:");
topSalaries.forEach(emp -> System.out.println(" " + emp));
// 3. 找出技術(shù)部工齡超過5年的員工
List<Employee> techLongService = employees.stream()
.filter(emp -> emp.getDepartment().equals("技術(shù)部"))
.filter(emp -> emp.getYearsOfService() > 5)
.collect(Collectors.toList());
System.out.println("\n技術(shù)部工齡超過5年的員工:");
techLongService.forEach(emp -> System.out.println(" " + emp +
", 工齡:" + emp.getYearsOfService() + "年"));
// 4. 按薪資區(qū)間分組
Map<String, List<Employee>> salaryRanges = employees.stream()
.collect(Collectors.groupingBy(emp -> {
int salary = emp.getSalary();
if (salary >= 25000) return "25000+";
else if (salary >= 20000) return "20000-24999";
else if (salary >= 15000) return "15000-19999";
else return "<15000";
}));
System.out.println("\n按薪資區(qū)間分組:");
salaryRanges.forEach((range, empList) ->
System.out.println(" " + range + ": " + empList.size() + "人"));
// 5. 統(tǒng)計(jì)各職位的人數(shù)
Map<String, Long> positionCount = employees.stream()
.collect(Collectors.groupingBy(Employee::getPosition, Collectors.counting()));
System.out.println("\n各職位人數(shù):");
positionCount.forEach((position, count) ->
System.out.println(" " + position + ": " + count + "人"));
// 6. 找出年齡在30-35歲之間的員工
List<Employee> ageRange = employees.stream()
.filter(emp -> emp.getAge() >= 30 && emp.getAge() <= 35)
.sorted(Comparator.comparingInt(Employee::getAge))
.collect(Collectors.toList());
System.out.println("\n年齡在30-35歲的員工:");
ageRange.forEach(emp -> System.out.println(" " + emp + ", 年齡:" + emp.getAge()));
// 7. 計(jì)算整體薪資分布
IntSummaryStatistics salaryStats = employees.stream()
.mapToInt(Employee::getSalary)
.summaryStatistics();
System.out.println("\n整體薪資統(tǒng)計(jì):");
System.out.println(" 人數(shù): " + salaryStats.getCount());
System.out.println(" 總和: " + salaryStats.getSum());
System.out.println(" 平均: " + (int)salaryStats.getAverage());
System.out.println(" 最高: " + salaryStats.getMax());
System.out.println(" 最低: " + salaryStats.getMin());
// 8. 查找每個(gè)部門薪資最高的員工
Map<String, Employee> highestPaidByDept = employees.stream()
.collect(Collectors.toMap(
Employee::getDepartment,
emp -> emp,
(e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2
));
System.out.println("\n各部門薪資最高的員工:");
highestPaidByDept.forEach((dept, emp) ->
System.out.println(" " + dept + ": " + emp.getName() + " (" + emp.getSalary() + ")"));
}
}運(yùn)行結(jié)果:
部門統(tǒng)計(jì):
技術(shù)部: 人數(shù)=4, 平均薪資=21250, 總和=85000
市場部: 人數(shù)=3, 平均薪資=13666, 總和=41000
財(cái)務(wù)部: 人數(shù)=1, 平均薪資=20000, 總和=20000薪資前3的員工:
吳九(技術(shù)部) - 架構(gòu)師, 薪資:30000
張三(技術(shù)部) - 高級工程師, 薪資:25000
趙六(市場部) - 市場經(jīng)理, 薪資:22000技術(shù)部工齡超過5年的員工:
吳九(技術(shù)部) - 架構(gòu)師, 薪資:30000, 工齡:10年
張三(技術(shù)部) - 高級工程師, 薪資:25000, 工齡:8年按薪資區(qū)間分組:
25000+: 2人
20000-24999: 2人
15000-19999: 1人
<15000: 3人各職位人數(shù):
高級工程師: 1人
中級工程師: 1人
初級工程師: 1人
市場經(jīng)理: 1人
銷售代表: 1人
財(cái)務(wù)經(jīng)理: 1人
架構(gòu)師: 1人
市場專員: 1人年齡在30-35歲的員工:
李四(技術(shù)部) - 中級工程師, 薪資:18000, 年齡:28
趙六(市場部) - 市場經(jīng)理, 薪資:22000, 年齡:35整體薪資統(tǒng)計(jì):
人數(shù): 8
總和: 146000
平均: 18250
最高: 30000
最低: 9000各部門薪資最高的員工:
技術(shù)部: 吳九 (30000)
市場部: 趙六 (22000)
財(cái)務(wù)部: 周八 (20000)
六、注意事項(xiàng)與最佳實(shí)踐
6.1 Stream的特點(diǎn)與限制
Stream只能消費(fèi)一次
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.count(); // 正確執(zhí)行
stream.count(); // 拋出IllegalStateException
解決方案: 每次需要使用Stream時(shí)重新創(chuàng)建
List<String> list = Arrays.asList("a", "b", "c");
list.stream().count(); // 第一次使用
list.stream().count(); // 第二次使用,重新創(chuàng)建Stream
Stream操作是惰性的
List<String> list = Arrays.asList("a", "bb", "ccc", "dddd");
Stream<String> stream = list.stream()
.filter(s -> {
System.out.println("filter: " + s);
return s.length() > 2;
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
}); // 此時(shí)不會執(zhí)行任何操作
stream.collect(Collectors.toList()); // 觸發(fā)執(zhí)行
執(zhí)行結(jié)果:
filter: a
filter: bb
filter: ccc
map: ccc
filter: dddd
map: dddd
6.2 性能考量
適當(dāng)使用基本類型Stream
// 避免裝箱操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 不推薦:使用Stream<Integer>
int sum1 = numbers.stream()
.reduce(0, Integer::sum); // 會發(fā)生裝箱拆箱
// 推薦:使用IntStream
int sum2 = numbers.stream()
.mapToInt(Integer::intValue)
.sum(); // 避免裝箱拆箱,性能更好合理使用并行流
List<Integer> numbers = IntStream.range(1, 1000000).boxed().collect(Collectors.toList());
// 順序流
long start1 = System.currentTimeMillis();
long sum1 = numbers.stream().reduce(0L, Long::sum);
long time1 = System.currentTimeMillis() - start1;
// 并行流
long start2 = System.currentTimeMillis();
long sum2 = numbers.parallelStream().reduce(0L, Long::sum);
long time2 = System.currentTimeMillis() - start2;
System.out.println("順序流: " + time1 + "ms");
System.out.println("并行流: " + time2 + "ms");使用并行流的注意事項(xiàng):
- 數(shù)據(jù)量較大時(shí)(通常>10000元素)才考慮使用并行流
- 數(shù)據(jù)源易于拆分(如ArrayList)時(shí)效果好
- 避免在并行流中使用共享狀態(tài)
- 操作本身耗時(shí)較長時(shí),并行流優(yōu)勢更明顯
避免過度使用Stream
// 簡單操作,傳統(tǒng)方式可能更清晰
List<String> names = Arrays.asList("張三", "李四", "王五");
// Stream方式
List<String> result = names.stream()
.filter(name -> name.startsWith("張"))
.collect(Collectors.toList());
// 傳統(tǒng)方式(更直接)
List<String> result2 = new ArrayList<>();
for (String name : names) {
if (name.startsWith("張")) {
result2.add(name);
}
}6.3 常見陷阱
避免在Stream中修改外部狀態(tài)
// 不推薦:修改外部變量
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int count = 0;
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> count++); // 并行流時(shí)可能出錯(cuò)
// 推薦:使用count()
long evenCount = numbers.stream()
.filter(n -> n % 2 == 0)
.count();注意Optional的使用
List<String> names = Arrays.asList("張三", "李四", "王五");
// 安全地處理Optional
Optional<String> result = names.stream()
.filter(name -> name.startsWith("趙"))
.findFirst();
// 不推薦:直接get()
// String s = result.get(); // 可能拋出NoSuchElementException
// 推薦:使用orElse()或其他安全方法
String s = result.orElse("未找到");
String s2 = result.orElseGet(() -> "未找到");
result.ifPresent(name -> System.out.println("找到: " + name));注意forEach的順序
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 順序流:保證順序 numbers.stream().forEach(n -> System.out.print(n + " ")); // 1 2 3 4 5 // 并行流:不保證順序 numbers.parallelStream().forEach(n -> System.out.print(n + " ")); // 順序不確定 // 并行流需要順序時(shí),使用forEachOrdered numbers.parallelStream().forEachOrdered(n -> System.out.print(n + " ")); // 1 2 3 4 5
避免在filter中使用副作用
// 不推薦:filter中有副作用
List<String> names = new ArrayList<>();
List<String> input = Arrays.asList("a", "b", "c");
input.stream()
.filter(s -> {
names.add(s); // 副作用,不可預(yù)測
return s.length() > 1;
})
.collect(Collectors.toList());
// 推薦:分離操作
List<String> names2 = input.stream()
.filter(s -> s.length() > 1)
.collect(Collectors.toList());6.4 最佳實(shí)踐總結(jié)
| 實(shí)踐 | 說明 |
|---|---|
| 使用方法引用 | String::length 比 s -> s.length() 更簡潔 |
| 鏈?zhǔn)秸{(diào)用 | 將多個(gè)操作串聯(lián)在一起,代碼更清晰 |
| 合理使用peek | 只在調(diào)試時(shí)使用,不要用于業(yè)務(wù)邏輯 |
| 善用Collectors | 熟練使用Collectors工具類的各種方法 |
| 注意空值處理 | 使用Optional處理可能為空的結(jié)果 |
| 考慮性能 | 大數(shù)據(jù)集考慮并行流,小數(shù)據(jù)集使用順序流 |
| 保持簡潔 | 過長的流水線考慮拆分 |
| 測試驗(yàn)證 | 確保Stream操作的正確性 |
七、總結(jié)
7.1 Stream API的核心價(jià)值
Java Stream API 是現(xiàn)代Java開發(fā)中不可或缺的工具,它帶來的核心價(jià)值包括:
- 代碼簡潔性:聲明式的編程風(fēng)格讓代碼更加簡潔、易讀
- 可讀性提升:鏈?zhǔn)秸{(diào)用清晰地表達(dá)了數(shù)據(jù)處理的意圖
- 并行處理能力:輕松實(shí)現(xiàn)并行計(jì)算,充分利用多核CPU
- 功能強(qiáng)大:提供了豐富的操作方法,滿足各種數(shù)據(jù)處理需求
- 函數(shù)式編程:推動(dòng)了Java向函數(shù)式編程范式的演進(jìn)
7.2 常用方法速查表
| 類別 | 方法 | 說明 |
|---|---|---|
| 創(chuàng)建 | stream() / parallelStream() | 從Collection創(chuàng)建 |
| Arrays.stream() | 從數(shù)組創(chuàng)建 | |
| Stream.of() | 從值創(chuàng)建 | |
| 篩選 | filter() | 過濾元素 |
| distinct() | 去重 | |
| limit() | 限制數(shù)量 | |
| skip() | 跳過元素 | |
| 映射 | map() | 元素轉(zhuǎn)換 |
| flatMap() | 扁平化映射 | |
| 排序 | sorted() | 排序 |
| 匹配 | allMatch() / anyMatch() / noneMatch() | 匹配檢查 |
| findFirst() / findAny() | 查找元素 | |
| 歸約 | reduce() | 歸約操作 |
| 收集 | collect() | 收集結(jié)果 |
| 統(tǒng)計(jì) | count() / max() / min() | 統(tǒng)計(jì)操作 |
7.3 結(jié)語
Java Stream API 徹底改變了我們處理集合數(shù)據(jù)的方式。掌握Stream不僅能讓你的代碼更加優(yōu)雅,還能提高開發(fā)效率。但要注意,Stream不是萬能的,在簡單場景下傳統(tǒng)方式可能更直接。選擇合適的工具,才能寫出更好的代碼。
到此這篇關(guān)于Java Stream流常用方法歸納整理及案例解析的文章就介紹到這了,更多相關(guān)Java Stream流常用方法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 實(shí)現(xiàn)程序的優(yōu)雅退出(詳細(xì)步驟)
Spring Boot 為我們提供了優(yōu)雅退出的功能,使應(yīng)用程序能夠在關(guān)閉時(shí)正常處理完所有當(dāng)前請求,避免請求被中斷導(dǎo)致數(shù)據(jù)丟失或不一致等問題,本文將全面介紹如何在 Spring Boot 應(yīng)用程序中實(shí)現(xiàn)優(yōu)雅退出,感興趣的朋友跟隨小編一起看看吧2024-03-03
Java分布式ID中Snowflake雪花算法應(yīng)用實(shí)現(xiàn)
Snowflake算法作為一種高效且易于實(shí)現(xiàn)的分布式ID生成方案,能夠很好地滿足分布式系統(tǒng)中對全局唯一ID的需求,本文就來介紹一下Java分布式ID中Snowflake雪花算法應(yīng)用實(shí)現(xiàn),感興趣的可以了解一下2024-07-07
SpringBoot實(shí)現(xiàn)日志文件分隔(根據(jù)日期和文件大小)
文章簡要介紹了如何在Spring Boot項(xiàng)目中配置日志文件路徑,包括在resource目錄下創(chuàng)建logback-spring.xml文件,并在yml配置文件中設(shè)置logging.file.path屬性來指定日志文件的輸出路徑2026-01-01
Spring Boot 全局異常處理策略設(shè)計(jì)之@ExceptionHandler 與&nb
SpringBoot全局異常處理策略設(shè)計(jì)@ExceptionHandler與@ControllerAdvice生效原理源碼解析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2026-01-01
Springcloud seata nacos環(huán)境搭建過程圖解
這篇文章主要介紹了Springcloud seata nacos環(huán)境搭建過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
關(guān)于FileChannel的transferFrom()方法的使用及說明
這篇文章主要介紹了關(guān)于FileChannel的transferFrom()方法的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
Spring?IOC容器Bean注解創(chuàng)建對象組件掃描
這篇文章主要為大家介紹了Spring?IOC容器Bean注解創(chuàng)建對象組件掃描,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Spring Boot 4 與 Spring Framework&nb
SpringBoot4和SpringFramework7帶來了大量新特性,包括升級到JakartaEE11、支持Java21、增強(qiáng)模塊化、優(yōu)化測試、增加彈性功能等,本文介紹Spring Boot 4 與 Spring Framework 7新特性、升級要點(diǎn)與實(shí)戰(zhàn)指南,感興趣的朋友跟隨小編一起看看吧2026-03-03

