1. Stream的概念
1.1.什么是Stream
Java 8引入了Lambda表达式和Stream API,Stream代表一个由数据元素组成的序列,支持一系列如过滤、映射、聚合等高级操作,但不支持元素的增加和删除。
1.2.Stream与集合、数组的关系
Stream与集合(如List、Set)、数组等数据结构紧密相关,但又有所不同。集合和数组用于存储数据,而Stream则提供了一种访问和处理这些数据的途径。完成操作后,Stream不会改变原始数据源,而是产生一个新的结果或者副作用(例如打印输出)。
2. 创建Stream
2.1.从集合创建Stream
Collection.stream()
在Java中,几乎所有实现了Collection
接口的集合类(如List
、Set
)都有一个stream()
方法,该方法可以返回一个代表该集合元素序列的Stream。
List<String> list = Arrays.asList("Java", "Kotlin", "Scala");
Stream<String> stream = list.stream();
Collection.parallelStream()
并行流可以在多核处理器上利用多线程并行处理集合中的数据,从而在适当的情况下提高性能,特别是对于大数据集的处理。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Stream<Integer> parallelStream = numbers.parallelStream();
2.2.从数组创建Stream
Stream API同样提供了从数组创建Stream的便捷方法。对于对象数组和基本数据类型数组,都有相应的方法来生成Stream。
数组对象
使用Arrays.stream(Object[] array)
String[] stringArray = new String[]{"Java", "Kotlin", "Scala"};
Stream<String> stringStream = Arrays.stream(stringArray);
基本数据类型数组
使用相应的方法,如IntStream
、LongStream
、DoubleStream
等
int[] intArray = new int[]{1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
2.3.静态创建方法
Stream.of()
Stream.of()
方法是一个静态方法,允许你直接从一个可变参数创建一个Stream。这个方法非常适合创建一个临时的Stream,特别是当需要快速生成一个包含几个元素的Stream时。
Stream<String> stream = Stream.of("Java", "Kotlin", "Scala");
IntStream.range( startInclusive, endExclusive )
startInclusive
:范围的起始值,包含在流中。endExclusive
:范围的结束值,不包含在流中。
IntStream rangeStream = IntStream.range(1, 4); // 创建一个包含1, 2, 3的Stream
2.4.创建无限Stream
Java的Stream API还支持创建无限序列的Stream。这可以通过Stream.iterate()
和Stream.generate()
方法实现。
创建无限Stream时需要特别小心,因为它们不会自然终止。通常,你需要使用limit()
等操作来限制结果的数量,否则可能会导致程序长时间运行或内存溢出。
Stream.iterate()
从一个初始值开始,然后反复应用一个函数来生成后续的元素。
// 无限Stream,生成一个无限序列的随机数
Stream<Double> randomStream = Stream.generate(Math::random);
Stream.generate()
使用一个Supplier
来生成每个元素。
// 无限Stream,从1开始,每次加1
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1);
2.5.其他特定类型
从文件创建
try (Stream<String> lines = Files.lines(Paths.get("path/to/file.txt"))) {lines.forEach(System.out::println);
} catch (IOException e) {e.printStackTrace();
}
从其他数据源创建
Java的NIO(New I/O)包提供了多种方式从不同数据源读取数据到Stream,如BufferedReader.lines()
。对于更复杂的数据源,可能需要结合InputStreamReader
、BufferedReader
等类来构造Stream。
3.操作类型
3.1.中间操作
中间操作 (Intermediate Operations)用于处理数据,但它们不会直接产生结果。相反,它们返回一个新的Stream,允许进一步的操作链式调用。
这些操作是惰性求值的,即直到遇到终止操作时才执行。
筛选与切片
filter 过滤
filter(Predicate<T> predicate)
:根据提供的谓词过滤出满足条件的元素。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.stream().filter(name -> name.length() > 4).forEach(System.out::println); // 输出: Charlie, David
distinct 去重复
distinct()
:去除重复元素。
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5);
numbers.stream().distinct().forEach(System.out::println); // 输出: 1, 2, 3, 4, 5
limit 取最多条
limit(long maxSize)
:限制Stream中元素的数量。
numbers.stream().distinct().limit(3).forEach(System.out::println); // 输出: 1, 2, 3
skip 跳过
skip(long n)
:跳过Stream开始的n个元素。
numbers.stream().distinct().skip(2).forEach(System.out::println); // 输出: 3, 4, 5
修改
peek 修改每个元素
peek()
:将每个元素修改值。
public class Person {String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public int getAge() { return age; }public void setName(String name){this.name = name;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return name + " (" + age + ")";}
}public class CustomSortExample {public static void main(String[] args) {List<Person> people = Arrays.asList(new Person("Alice", 30),new Person("Bob", 25),new Person("Charlie", 30),new Person("David", 25));List<Person> newPeoples = people.stream().peek(person->{person.setAge(person.getAge() + 10);}).collect(Collectors.toList());newPeoples.forEach(System.out::println);}
}
映射
map
map(Function<T, R> mapper)
:将每个元素转换为另一种形式或结构。
List<String> words = Arrays.asList("hello", "world");
words.stream().map(String::toUpperCase).forEach(System.out::println); // 输出: HELLO, WORLD
转换结构
List<Map<String, Objcet>> peopleMap = people.stream().map(person->{Map<String, Objcet> map = new HashMap<>();map.put("aaa", person.getAge() );return map}).collect(Collectors.toList());peopleMap.forEach(System.out::println);
flatMap
flatMap(Function<T, Stream<R>> mapper)
:将每个元素转换为另一个Stream,然后将所有Stream连接成一个单一的Stream。
List<String> sentences = Arrays.asList("Hello world", "Java is fun");
sentences.stream().flatMap(sentence -> Arrays.stream(sentence.split(" "))).forEach(System.out::println); // 输出: Hello, world, Java, is, fun
排序
sorted自然顺序
sorted()
:自然排序,根据元素的自然顺序。
List<Integer> randomNumbers = Arrays.asList(5, 9, 1, 4, 7);
randomNumbers.stream().sorted().forEach(System.out::println); // 输出: 1, 4, 5, 7, 9
sorted 自定义排序
sorted(Comparator<T> comparator)
:自定义排序规则。
class Person {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public int getAge() { return age; }@Overridepublic String toString() {return name + " (" + age + ")";}
}public class CustomSortExample {public static void main(String[] args) {List<Person> people = Arrays.asList(new Person("Alice", 30),new Person("Bob", 25),new Person("Charlie", 30),new Person("David", 25));// 使用自定义比较器进行排序Comparator<Person> byAgeThenName = Comparator.comparing(Person::getAge).reversed() // 首先按年龄降序.thenComparing(Person::getName); // 年龄相同则按姓名升序List<Person> sortedPeople = people.stream().sorted(byAgeThenName).collect(Collectors.toList());sortedPeople.forEach(System.out::println);}
}
并行处理与合并
parallel 并行流
parallel()
:将流转换为并行流。
randomNumbers.parallelStream().filter(num -> num % 2 == 0).forEach(System.out::println); // 结果顺序可能不同,因为是并行处理
sequential 换回顺序流
sequential()
:将流转换回顺序流,意味着之后的操作将在单线程中顺序执行,按照数据源的自然顺序或之前指定的排序进行。当不需要并行处理或者并行处理带来额外开销(如线程同步成本)大于其带来的性能提升时,可以使用此方法。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 假设这是之前并行处理的代码
numbers.parallelStream().map(n -> {// 模拟耗时操作Thread.sleep(100);return n * 2;}).sequential() // 转换为顺序流.forEach(System.out::println);
unordered 去除排序保证,允许优化
unordered()
:去除排序保证,允许优化。此方法去除了Stream的排序保证,允许某些操作(特别是并行操作)进行潜在的优化,因为不再需要维持元素的顺序。这对于那些不需要特定顺序就可以完成的操作来说,可能会提高效率。注意,只有在Stream来源本身没有固有排序(如HashSet转换的Stream)或通过无序操作(如unordered()
)创建的Stream上使用此方法才有意义。
List<String> words = Arrays.asList("Java", "Python", "C++", "Java", "Ruby");long javaCount = words.stream().unordered() // 允许无序处理以优化.filter(word -> word.equals("Java")).count();System.out.println("Count of 'Java': " + javaCount); // 输出:Count of 'Java': 2
基本类型转换
boxed() 原始类型流
在原始类型流的接口中,如IntStream
, LongStream
, DoubleStream
,boxed()
方法用于将原始类型流转换为包装类型的对象流(如Stream<Integer>
, Stream<Long>
, Stream<Double>
)。
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> integerStream = intStream.boxed();
integerStream.forEach(System.out::println);
3.2.终止操作
终止操作 (Terminal Operations)会消费Stream,产生一个结果或副作用,并且执行后Stream不能再被使用。这些操作触发实际的计算。
查找与匹配
findFirst
findFirst()
:返回第一个元素(对于有序流有定义)。
Optional<String> first = words.stream().filter(word -> word.startsWith("h")).findFirst();
System.out.println(first.orElse("Not found")); // 输出: hello
anyMatch
anyMatch(Predicate<T> predicate)
:检查是否有至少一个元素满足条件。
boolean hasShortWord = words.stream().anyMatch(word -> word.length() < 5);
System.out.println(hasShortWord); // 输出: true
allMatch
allMatch(Predicate<T> predicate)
:检查是否所有元素都满足条件。
noneMatch
noneMatch(Predicate<T> predicate)
:检查是否没有元素满足条件。
归约
reduce
reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
:将流中的元素累积成一个单一的结果,常用于求和、乘积等。
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
System.out.println(sum.orElse(0)); // 输出: 15
收集
collect
collect(Collector<T, A, R> collector)
:将流转换为另一种数据结构或计算单一结果,如列表、集合、映射等。
-
Collectors.toList()
,Collectors.toSet()
,List<String> upperCaseWords = words.stream().map(String::toUpperCase).collect(Collectors.toList()); System.out.println(upperCaseWords); // 输出: [HELLO, WORLD]
-
Collectors.toMap()
基本应用:键值一对一映射
import java.util.*; import java.util.stream.*;class Person {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}public String getName() { return name; }public int getAge() { return age; } }public class ToMapExample {public static void main(String[] args) {List<Person> people = Arrays.asList(new Person("Alice", 30),new Person("Bob", 25),new Person("Charlie", 30));Map<String, Person> mapByName = people.stream().collect(Collectors.toMap(Person::getName, p -> p));mapByName.forEach((name, person) -> System.out.println(name + ": " + person.getAge()));} }
处理键冲突:合并函数
Map<String, Integer> mapWithMerge = people.stream().collect(Collectors.toMap(Person::getName, Person::getAge, (oldValue, newValue) -> oldValue + newValue // 合并函数,这里简单相加处理冲突));mapWithMerge.forEach((name, ageSum) -> System.out.println(name + ": " + ageSum));
如果有同名的人,他们的年龄会被相加
groupingBy
groupingBy()
是 Java Stream API 中的一个收集器(Collector),它用于将流中的元素按照某个属性或者函数的结果进行分组。下面我将通过一个例子来说明如何使用 groupingBy()
方法。
假设我们有一个列表,里面存储的是员工(Employee)对象,每个员工有姓名(name)和部门(department)两个属性。现在我们想按部门对员工进行分组,可以这样做:
首先,定义一个简单的 Employee
类:
public class Employee {private String name;private String department;// 构造方法、getter和setter省略...
}
然后,创建一个员工列表并使用 groupingBy()
进行分组:
import java.util.*;
import java.util.stream.Collectors;public class GroupingExample {public static void main(String[] args) {List<Employee> employees = Arrays.asList(new Employee("Alice", "HR"),new Employee("Bob", "IT"),new Employee("Charlie", "HR"),new Employee("David", "Finance"),new Employee("Eva", "IT"));// 使用groupingBy按部门分组Map<String, List<Employee>> employeesByDepartment = employees.stream().collect(Collectors.groupingBy(Employee::getDepartment));// 打印结果for (Map.Entry<String, List<Employee>> entry : employeesByDepartment.entrySet()) {System.out.println("Department: " + entry.getKey());for (Employee employee : entry.getValue()) {System.out.println(" Employee: " + employee.getName());}}}
}
在这个例子中,Collectors.groupingBy(Employee::getDepartment)
表示根据员工的部门属性进行分组,最终得到的结果是一个 Map<String, List<Employee>>
,其中键是部门名称,值是该部门的所有员工列表。
输出结果将会是按部门分组的员工列表,类似于这样:
Department: HREmployee: AliceEmployee: Charlie
Department: ITEmployee: BobEmployee: Eva
Department: FinanceEmployee: David
这个例子展示了如何使用 groupingBy()
来对集合中的对象进行分组处理,是一个非常实用且强大的Stream API特性。
partitioningBy
partitioningBy()
聚合统计
count
count()
:返回流中元素的数量。
long evenCount = numbers.stream().filter(num -> num % 2 == 0).count();
System.out.println(evenCount); // 输出: 3
min
min(Comparator<? super T> comparator)
,max(Comparator<? super T> comparator)
:找出最小或最大元素。
average
average()
:计算平均值(仅适用于数值类型流)。
连接字符串
joining
Collectors.joining(CharSequence delimiter)
:在收集器中使用,连接流中的元素为一个字符串。
String.join(CharSequence delimiter, CharSequence... elements)
:静态方法,非Stream操作,但常与Stream结合使用来连接字符串。
String joinedWords = words.stream().collect(Collectors.joining(", "));
System.out.println(joinedWords); // 输出: hello, world
输出
forEach
forEach(Consumer<? super T> action)
:对流中的每个元素执行给定操作。
numbers.stream().forEach(num -> System.out.println("Number: " + num));
// 输出: Number: 1, Number: 2, ... (按顺序)
forEachOrdered
forEachOrdered(Consumer<? super T> action)
:与forEach相似,但在顺序流中保证元素的遍历顺序。