一、概述
在使用stream
之前,先理解Optional
。
Optional
是Java 8
引入的一个容器类,用于处理可能为空的值。它提供了一种优雅的方式来处理可能存在或不存在的值,避免了空指针异常。
Optional
的主要特点如下:
- 可能为空:
Optional
可以包含一个非空的值,也可以表示为空。 - 避免空指针异常:通过使用
Optional
,可以避免在访问可能为空的值时出现空指针异常。 - 显式判断:使用
Optional
需要显式地判断值是否存在,以便进行相应的处理。 - 函数式操作:
Optional
提供了一系列的函数式操作方法,如map()
、filter()
、orElse()
等,方便对Optional
中的值进行转换、过滤和默认值处理。
使用Stream
可以对集合或数组中的元素进行各种转换、过滤和映射等操作,以实现更简洁、灵活和函数式的编程风格。下面是使用Stream
的一般步骤:
- 创建
Stream
:通过集合、数组、IO
通道或Stream
的静态方法来创建一个Stream
对象。 - 中间操作(
Intermediate Operations
):使用中间操作方法对Stream进行转换、过滤、映射等操作,返回一个新的Stream对象。常见的中间操作包括filter()
、map()
、sorted()
、distinct()
等。 - 终止操作(
Terminal Operations
):使用终止操作方法对Stream
进行最终的计算或收集操作,返回一个结果或一个最终的集合。常见的终止操作包括forEach()
、collect()
、reduce()
、min()
、max()
等。
二、Stream的创建
在Java中,可以使用多种方式来创建Stream对象。下面是一些常见的创建Stream的方法:
1. 通过集合创建Stream
通过集合创建Stream是一种常见的方式,Java8
中的 Collection
接口被扩展,提供两个获取流的方法 :
Stream stream()
: 返回一个顺序流Stream parallelStream()
: 返回一个并行流
Stream<Integer> stream1 = Arrays.asList(1,2,3,4).stream();
Stream<Integer> stream2 = Arrays.asList(1,2,3,4).parallelStream();
2. 通过数组创建Stream
通过数组创建Stream
可以使用Arrays.stream()
方法来实现。该方法接受一个数组作为参数,并返回一个对应类型的Stream
对象。
int[] array = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(array);
3. 通过Stream的静态方法创建Stream
- 通过
Stream
的静态方法,可以使用of()
、iterate()
和generate()
等方法来创建Stream
对象。 - 使用
IntStream
、LongStream
、DoubleStream
的static
方法创建有限流,可以使用of()
、range()
和rangeClosed()
等方法来创建Stream
对象。 - 使用随机数类的
ints()
方法创建无限数值流
// 使用of()方法创建Stream
Stream<String> stream1 = Stream.of("apple", "banana", "orange");
// 使用iterate()方法创建Stream
Stream<Integer> stream2 = Stream.iterate(0, n -> n + 2).limit(10);
// 使用generate()方法创建Stream
Stream<Double> stream3 = Stream.generate(Math::random).limit(5);
// 使用of()方法创建IntStream
IntStream.of(new int[]{1, 2, 3});
// 使用range()方法创建IntStream
IntStream.range(1, 3);
// 使用rangeClosed()方法创建IntStream
IntStream.rangeClosed(1, 3);
// 使用随机数类的ints()方法创建无限数值流
Random random = new Random();
IntStream ints = random.ints();
4. 通过IO通道创建Stream
- 使用
BufferedReader
的lines
方法从文件中获得行的流 Files
类的操作路径的方法,如list
、find
、walk
、lines
等
// 使用BufferedReader的lines方法从文件中获得行的流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));
Stream<String> lines = bufferedReader.lines();
Path path = Paths.get("file.txt");
Stream<String> lines = Files.lines(path);
5. 通过其他类提供的创建Stream
BitSet
数值流Pattern
将字符串分隔成流JarFile
读取jar
文件流
// BitSet数值流
IntStream stream = new BitSet().stream();
// Pattern 将字符串分隔成流
Pattern pattern = compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
// JarFile 读取jar文件流
Stream<JarEntry> stream = new JarFile("").stream();
这些方法提供了不同的方式来创建Stream对象,可以根据具体的需求选择适当的创建方式。创建Stream后,可以使用Stream的中间操作和终止操作来对数据进行处理和操作。
需要注意的是,Stream对象是一次性使用的,一旦对Stream进行了终止操作,就不能再对同一个Stream进行其他操作。如果需要对同一组数据进行多个操作,可以创建多个Stream对象来实现。
三、中间操作
3.1 筛选(filter
)
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
filter()
是Java Stream API中的一个中间操作方法,用于根据指定的条件过滤流中的元素。它接受一个Predicate
函数式接口作为参数,该接口定义了一个用于判断元素是否满足条件的方法。
filter()
方法的语法:Stream<T> filter(Predicate<? super T> predicate)
其中,T
表示流中的元素类型,predicate
表示用于判断元素是否满足条件的Predicate
对象。
filter()
方法的工作原理如下:
- 对于流中的每个元素,
filter()
方法会调用传入的Predicate
对象的test()
方法,将当前元素作为参数传递给test()
方法。 - 如果
test()
方法返回true
,则表示当前元素满足条件,会被保留在新的流中。 - 如果
test()
方法返回false
,则表示当前元素不满足条件,会被过滤掉,不包含在新的流中。
下面是一个示例代码,演示了如何使用filter()方法过滤出偶数:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamFilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用filter()方法过滤出偶数
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 输出结果
System.out.println(evenNumbers); // [2, 4, 6, 8, 10]
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用filter()
方法过滤出偶数,通过传入的Lambda
表达式判断元素是否为偶数。最后,我们使用collect()
方法将过滤后的结果收集到一个新的列表中,并输出结果。
通过使用filter()
方法,我们可以根据指定的条件过滤流中的元素,只保留满足条件的元素,实现对数据的筛选和过滤操作。
3.2 元素转换(peek
/map
/flatMap
)
在Java的Stream API中,peek()
、map()
和flatMap()
是常用的中间操作方法,用于对流中的元素进行转换、处理和操作。
peek()
方法:
peek()
方法接受一个Consumer
函数式接口作为参数,对流中的每个元素执行指定的操作,并返回一个新的流。peek()
方法可以用于调试和观察流中的元素,但不会改变流中元素的内容。peek()
方法是一个中间操作,它返回的是与原始流相同类型的新流。
map()
方法:
map()
方法接受一个Function
函数式接口作为参数,将流中的每个元素映射为另一个元素,并返回一个新的流。map()
方法可以用于对流中的元素进行转换、提取或计算等操作。map()
方法是一个中间操作,它返回的是与原始流中元素类型不同的新流。
flatMap()
方法:
flatMap()
方法接受一个Function
函数式接口作为参数,将流中的每个元素映射为一个流,并将所有流连接成一个新的流。flatMap()
方法可以用于扁平化嵌套的流结构,将多个流合并为一个流。flatMap()
方法是一个中间操作,它返回的是与原始流中元素类型不同的新流。
下面是一个示例代码,演示了peek()、map()和flatMap()的使用:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamOperationsExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World", "Java");
// 使用peek()方法调试和观察流中的元素
List<String> peekResult = words.stream()
.peek(System.out::println)
.collect(Collectors.toList());
// 使用map()方法将每个单词转换为大写
List<String> mapResult = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 使用flatMap()方法将每个单词拆分为字母
List<String> flatMapResult = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
// 输出结果
System.out.println("Peek Result: " + peekResult);
System.out.println("Map Result: " + mapResult);
System.out.println("FlatMap Result: " + flatMapResult);
}
}
在上面的示例中,我们首先创建了一个包含单词的列表。然后,我们使用peek()
方法对流中的元素进行调试和观察,并使用map()
方法将每个单词转换为大写,最后使用flatMap()
方法将每个单词拆分为字母。最终,我们使用collect()
方法将结果收集到一个新的列表中,并输出结果。
通过使用peek()
、map()
和flatMap()
等方法,我们可以对流中的元素进行转换、处理和操作,实现更加灵活和函数式的编程风格。
3.3 排序(sorted
)
sorted()
是Java Stream API中的一个中间操作方法,用于对流中的元素进行排序。它可以按照自然顺序或者通过自定义的Comparator
来进行排序。
sorted()
方法的语法:Stream<T> sorted()
/ Stream<T> sorted(Comparator<? super T> comparator)
其中,T表示流中的元素类型,comparator表示用于比较元素的Comparator对象。
sorted()
方法的工作原理如下:
- 对于无参的
sorted()
方法,它会使用元素的自然顺序进行排序。元素类型必须实现Comparable
接口,否则会抛出ClassCastException
。 - 对于带有
Comparator
参数的sorted()
方法,它会使用指定的Comparator
对象来进行排序。Comparator
定义了元素之间的比较规则。
下面是一个示例代码,演示了如何使用sorted()
方法对整数列表进行排序:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamSortedExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
// 使用sorted()方法对整数列表进行排序
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
// 输出结果
System.out.println(sortedNumbers); // [1, 2, 3, 5, 8, 9]
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用sorted()
方法对整数列表进行排序,默认按照自然顺序进行排序。最后,我们使用collect()
方法将排序后的结果收集到一个新的列表中,并输出结果。
通过使用sorted()
方法,我们可以对流中的元素进行排序,使得元素按照指定的顺序排列。可以根据需要使用自然顺序或自定义的Comparator
来进行排序操作。
3.4 截取/跳过(limit
/skip
)
limit()
和skip()
是Java Stream API中的两个中间操作方法,用于限制流中元素的数量。
limit()
方法:
limit()
方法接受一个long
类型的参数,用于限制流中元素的数量。- 它返回一个新的流,其中包含原始流中的前
n
个元素(如果原始流中的元素不足n
个,则返回所有元素)。
skip()
方法:
skip()
方法接受一个long
类型的参数,用于跳过流中的前n
个元素。- 它返回一个新的流,其中包含原始流中剩余的元素(如果原始流中的元素少于
n
个,则返回空流)。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamLimitSkipExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用limit()方法限制流中元素的数量
List<Integer> limitedNumbers = numbers.stream()
.limit(3)
.collect(Collectors.toList());
// 使用skip()方法跳过流中的前N个元素
List<Integer> skippedNumbers = numbers.stream()
.skip(2)
.collect(Collectors.toList());
// 输出结果
System.out.println(limitedNumbers); // [1, 2, 3]
System.out.println(skippedNumbers); // [3, 4, 5]
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用limit()
方法限制流中元素的数量为3个,通过collect()
方法将结果收集到一个新的列表中。接着,我们使用skip()
方法跳过流中的前2个元素,同样通过collect()
方法将结果收集到另一个新的列表中。最后,我们输出限制和跳过操作后的结果。
通过使用limit()
和skip()
方法,我们可以对流中的元素进行数量的限制和跳过操作,实现对数据的筛选和截取。
3.5 合并(concat
)
concat()
是Java Stream API中的一个静态方法,用于将两个流连接起来形成一个新的流。它接受两个相同类型的流作为参数,并返回一个包含两个流中所有元素的新流。
concat()
方法的语法:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
其中,T
表示流中的元素类型,a
和b
表示要连接的两个流。
concat()
方法的工作原理如下:
- 它会将第一个流的所有元素放在新流中,然后将第二个流的所有元素追加到新流的末尾。
- 新流中的元素顺序与原始流的顺序保持一致。
下面是一个示例代码,演示了如何使用concat()
方法连接两个流:
import java.util.stream.Stream;
public class StreamConcatExample {
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5, 6);
// 使用concat()方法连接两个流
Stream<Integer> concatenatedStream = Stream.concat(stream1, stream2);
// 输出结果
concatenatedStream.forEach(System.out::println); // 1, 2, 3, 4, 5, 6
}
}
在上面的示例中,我们首先创建了两个整数流。然后,我们使用concat()
方法将这两个流连接起来,形成一个新的流。最后,我们使用forEach()
方法遍历并打印连接后的结果。
通过使用concat()
方法,我们可以将两个流连接起来,形成一个包含两个流中所有元素的新流。这对于需要合并多个流的场景非常有用。
3.6 去重(distinct
)
distinct()
是Java Stream API中的一个中间操作方法,用于去除流中的重复元素。它会返回一个新的流,其中包含原始流中的所有不重复的元素。
distinct()
方法使用元素的equals()
方法来判断元素是否重复。如果两个元素相等,则只保留第一个出现的元素,后续出现的相同元素将被过滤掉。
distinct()
方法的语法:Stream<T> distinct()
其中,T
表示流中的元素类型。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5);
// 使用distinct()方法去除重复元素
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
// 输出结果
System.out.println(distinctNumbers); // [1, 2, 3, 4, 5]
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用distinct()
方法去除列表中的重复元素,并通过collect()
方法将结果收集到一个新的列表中。最后,我们输出去除重复元素后的结果。
通过使用distinct()
方法,我们可以方便地去除流中的重复元素,得到一个只包含不重复元素的新流。这对于数据去重和筛选操作非常有用。
四、终止操作
4.1 遍历/匹配(foreach
/findAny
/findFirst
/anyMatch
/allMatch
/noneMatch
)
在Java Stream API中,forEach()
、findAny()
、findFirst()
和anyMatch()
、allMatch()
、noneMatch()
是常用的终止操作方法,用于对流中的元素进行遍历、查找和匹配。
forEach()
方法:
forEach()
方法接受一个Consumer
函数式接口作为参数,对流中的每个元素执行指定的操作。- 它没有返回值,只是对流中的每个元素进行操作。
findAny()
和findFirst()
方法:
findAny()
方法返回流中的任意一个元素,如果流为空则返回Optional.empty()
。findFirst()
方法返回流中的第一个元素,如果流为空则返回Optional.empty()
。
anyMatch()
、allMatch()
和noneMatch()
方法:
anyMatch()
方法接受一个Predicate
函数式接口作为参数,判断流中是否存在满足指定条件的元素。allMatch()
方法接受一个Predicate
函数式接口作为参数,判断流中的所有元素是否都满足指定条件。noneMatch()
方法接受一个Predicate
函数式接口作为参数,判断流中是否没有任何元素满足指定条件。- 这些方法返回一个
boolean
类型的结果,表示是否存在满足条件的元素。
import java.util.Arrays;
import java.util.List;
public class StreamTerminalOperationsExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用forEach()方法遍历每个元素
numbers.stream()
.forEach(System.out::println);
// 使用findAny()方法找到任意一个元素
Integer anyNumber = numbers.stream()
.findAny()
.orElse(null);
// 使用findFirst()方法找到第一个元素
Integer firstNumber = numbers.stream()
.findFirst()
.orElse(null);
// 使用anyMatch()方法判断是否存在大于3的元素
boolean anyGreaterThanThree = numbers.stream()
.anyMatch(n -> n > 3);
// 使用allMatch()方法判断是否所有元素都小于10
boolean allLessThanTen = numbers.stream()
.allMatch(n -> n < 10);
// 使用noneMatch()方法判断是否没有任何元素等于6
boolean noneEqualsSix = numbers.stream()
.noneMatch(n -> n == 6);
// 输出结果
System.out.println("Any Number: " + anyNumber);
System.out.println("First Number: " + firstNumber);
System.out.println("Any Greater Than Three: " + anyGreaterThanThree);
System.out.println("All Less Than Ten: " + allLessThanTen);
System.out.println("None Equals Six: " + noneEqualsSix);
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行遍历、查找和匹配操作,并输出结果。
通过使用这些终止操作方法,我们可以对流中的元素进行遍历、查找和匹配操作,得到相应的结果。
4.2 聚合(max
/min
/count
)
在Java Stream API中,max()
、min()
和count()
是常用的终止操作方法,用于获取流中的最大值、最小值和元素数量。
max()
方法:
max()
方法接受一个Comparator
函数式接口作为参数,用于比较流中的元素。- 它返回流中的最大元素,如果流为空则返回
Optional.empty()
。
min()
方法:
min()
方法接受一个Comparator
函数式接口作为参数,用于比较流中的元素。- 它返回流中的最小元素,如果流为空则返回
Optional.empty()
。
count()
方法:
count()
方法返回流中的元素数量,返回一个long
类型的结果。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamTerminalOperationsExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用max()方法找到最大值
Optional<Integer> maxNumber = numbers.stream()
.max(Integer::compare);
// 使用min()方法找到最小值
Optional<Integer> minNumber = numbers.stream()
.min(Integer::compare);
// 使用count()方法计算元素数量
long count = numbers.stream()
.count();
// 输出结果
System.out.println("Max Number: " + maxNumber.orElse(null));
System.out.println("Min Number: " + minNumber.orElse(null));
System.out.println("Count: " + count);
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行最大值、最小值和计数操作,并输出结果。
通过使用这些终止操作方法,我们可以方便地获取流中的最大值、最小值和元素数量。需要注意的是,max()
和min()
方法返回的是Optional
类型的结果,因为流中可能为空。
4.3 归约(reduce
)
reduce()
是Java Stream API中的一个终止操作方法,用于将流中的元素按照指定的规约操作进行合并,返回一个最终的结果。归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
reduce()
方法接受一个BinaryOperator
函数式接口作为参数,该接口定义了一个二元操作,用于将两个元素进行合并。它还可以接受一个初始值作为累加器的初始值。
reduce()
方法的语法:
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
其中,T
表示流中的元素类型,accumulator
表示用于合并元素的操作,identity
表示累加器的初始值。
reduce()
方法的工作原理如下:
- 对于流中的第一个元素,将其作为累加器的初始值。
- 对于后续的每个元素,使用累加器和当前元素进行合并操作,得到一个新的累加器值。
- 最终返回合并后的累加器值。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用reduce()方法对整数列表进行求和
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum);
// 输出结果
System.out.println("Sum: " + sum.orElse(0));
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用reduce()
方法对整数列表进行求和,通过传入的Integer::sum
方法作为累加器进行合并操作。最后,我们使用orElse()
方法获取求和结果,并输出结果。
通过使用reduce()
方法,我们可以对流中的元素进行合并操作,实现对数据的聚合和规约。需要注意的是,reduce()
方法返回的是Optional
类型的结果,因为流中可能为空。
4.4 收集(collect
)
collect
,收集,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
collect()
用于将流中的元素收集到一个集合或其他数据结构中。
collect()
方法接受一个Collector
对象作为参数,该对象定义了如何将流中的元素进行收集和组合。Collector
接口提供了一系列静态方法来创建常见的收集器实例。
下面是collect()
方法的语法:<R> R collect(Collector<? super T, A, R> collector)
其中,T
表示流中的元素类型,A
表示中间结果的类型,R
表示最终结果的类型。
collect()
方法的工作原理如下:
- 它会使用
Collector
对象中定义的逻辑,对流中的元素进行收集和组合。 - 最终返回一个包含收集结果的对象,可以是
List
、Set
、Map
等。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamCollectExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用collect()方法将偶数收集到一个新的列表中
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 输出结果
System.out.println(evenNumbers); // [2, 4]
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用filter()
方法过滤出偶数,并通过collect()
方法将结果收集到一个新的列表中。最后,我们输出收集后的结果。
通过使用collect()
方法,我们可以方便地将流中的元素收集到一个集合或其他数据结构中,实现对数据的聚合和收集操作。
4.4.1 归集(toList
/toSet
/toMap
)
因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList
、toSet
和toMap
比较常用,另外还有toCollection
、toConcurrentMap
等复杂一些的用法。
在Java Stream API中,有多个方法可用于将流中的元素收集到不同类型的集合或映射中。
toList()
方法:
-
toList()
方法将流中的元素收集到一个列表中。- 它返回一个包含流中所有元素的新列表。
toSet()
方法:
-
toSet()
方法将流中的元素收集到一个集合中。- 它返回一个包含流中所有元素的新集合。
toMap()
方法:
-
toMap()
方法将流中的元素收集到一个映射中。- 它接受两个
Function
函数式接口作为参数,用于提取键和值,并返回一个包含流中所有键值对的新映射。
toCollection()
方法:
-
toCollection()
方法将流中的元素收集到指定类型的集合中。- 它接受一个
Supplier
函数式接口作为参数,用于提供一个自定义的集合实例。
toConcurrentMap()
方法:
-
toConcurrentMap()
方法将流中的元素收集到一个并发映射中。- 它接受三个
Function
函数式接口作为参数,用于提取键、值和处理键冲突的方式。
这些方法都是终止操作,它们根据需要将流中的元素收集到不同类型的集合或映射中。根据具体的需求和数据结构,可以选择适当的方法来进行元素的收集和聚合操作。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class StreamCollectExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用toList()方法将元素收集到列表中
List<Integer> list = numbers.stream()
.collect(Collectors.toList());
// 使用toSet()方法将元素收集到集合中
Set<Integer> set = numbers.stream()
.collect(Collectors.toSet());
// 使用toMap()方法将元素收集到映射中
Map<Integer, String> map = numbers.stream()
.collect(Collectors.toMap(
num -> num,
num -> "Value" + num
));
// 使用toCollection()方法将元素收集到自定义集合中
Set<Integer> customSet = numbers.stream()
.collect(Collectors.toCollection(() -> new CustomSet<>()));
// 使用toConcurrentMap()方法将元素收集到并发映射中
Map<Integer, String> concurrentMap = numbers.stream()
.collect(Collectors.toConcurrentMap(
num -> num,
num -> "Value" + num,
(v1, v2) -> v1 + ", " + v2,
ConcurrentHashMap::new
));
// 输出结果
System.out.println("List: " + list); // [1, 2, 3, 4, 5]
System.out.println("Set: " + set); // [1, 2, 3, 4, 5]
System.out.println("Map: " + map); // {1=Value1, 2=Value2, 3=Value3, 4=Value4, 5=Value5}
System.out.println("Custom Set: " + customSet);
System.out.println("Concurrent Map: " + concurrentMap);
}
}
class CustomSet<T> extends HashSet<T> {
// 自定义集合类
}
通过使用toList()
、toSet()
和toMap()
等方法,我们可以方便地将流中的元素收集到列表、集合或映射中,实现对数据的聚合和收集操作。
通过使用toCollection()
和toConcurrentMap()
等方法,我们可以方便地将流中的元素收集到自定义的集合或并发映射中,实现对数据的聚合和收集操作。
4.4.2 统计(count
/averaging
/max
(min
)/summing
/summarizing
)
在Java Stream API中,有多个终止操作方法可以用于对流中的元素进行计数、平均值、最大值、最小值、求和和统计等操作。
count()
方法:
-
count()
方法返回流中的元素数量。- 它返回一个
long
类型的结果,表示流中的元素数量。
averagingXxx()
方法:
-
averagingXxx()
方法用于计算流中元素的平均值,其中Xxx
可以是Int
、Long
或Double
。- 它返回一个
double
类型的结果,表示流中元素的平均值。
maxBy()
和minBy()
方法:
-
maxBy()
方法接受一个Comparator
函数式接口作为参数,返回流中的最大元素。minBy()
方法接受一个Comparator
函数式接口作为参数,返回流中的最小元素。- 它们返回一个
Optional
对象,表示流中的最大或最小元素。
summingXxx()
方法:
-
summingXxx()
方法用于对流中的元素进行求和,其中Xxx
可以是Int
、Long
或Double
。- 它返回一个
Xxx
类型的结果,表示流中元素的总和。
summarizingXxx()
方法:
-
summarizingXxx()
方法用于对流中的元素进行统计,其中Xxx
可以是Int
、Long
或Double
。- 它返回一个包含元素数量、总和、平均值、最大值和最小值的
XxxSummaryStatistics
对象。
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class StreamStatisticsExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用count()方法统计元素数量
long count = numbers.stream()
.count();
// 使用averagingXxx()方法计算平均值
double average = numbers.stream()
.collect(Collectors.averagingInt(Integer::intValue));
// 使用maxBy()方法找到最大值
Optional<Integer> max = numbers.stream()
.max(Integer::compare);
// 使用minBy()方法找到最小值
Optional<Integer> min = numbers.stream()
.min(Integer::compare);
// 使用summingXxx()方法求和
int sum = numbers.stream()
.collect(Collectors.summingInt(Integer::intValue));
// 使用summarizingXxx()方法进行统计
IntSummaryStatistics statistics = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
// 输出结果
System.out.println("Count: " + count);
System.out.println("Average: " + average);
System.out.println("Max: " + max.orElse(null));
System.out.println("Min: " + min.orElse(null));
System.out.println("Sum: " + sum);
System.out.println("Statistics: " + statistics);
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用不同的终止操作方法对流中的元素进行计数、平均值、最大值、最小值、求和和统计操作,并输出结果。
通过使用这些终止操作方法,我们可以方便地对流中的元素进行各种统计操作,得到相应的结果。
4.4.3 分组(partitioningBy
/groupingBy
)
在Java Stream API中,partitioningBy()
和groupingBy()
是用于对流中的元素进行分区和分组的收集器。
partitioningBy()
方法:
partitioningBy()
方法接受一个Predicate
函数式接口作为参数,用于将流中的元素分为满足条件和不满足条件的两个部分。- 它返回一个
Map<Boolean, List<T>>
类型的结果,其中Boolean
表示分区的键,List<T>
表示满足或不满足条件的元素列表。
groupingBy()
方法:
groupingBy()
方法接受一个Function
函数式接口作为参数,用于根据指定的分类函数对流中的元素进行分组。- 它返回一个
Map<K, List<T>>
类型的结果,其中K表示分组的键,List<T>
表示具有相同键的元素列表。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamPartitioningGroupingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 使用partitioningBy()方法将奇偶数分区
Map<Boolean, List<Integer>> partitionedNumbers = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 使用groupingBy()方法将数字按照奇偶分组
Map<String, List<Integer>> groupedNumbers = numbers.stream()
.collect(Collectors.groupingBy(n -> n % 2 == 0 ? "Even" : "Odd"));
// 输出结果
System.out.println("Partitioned Numbers: " + partitionedNumbers);
System.out.println("Grouped Numbers: " + groupedNumbers);
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用partitioningBy()
方法将列表中的元素按照奇偶数进行分区,使用groupingBy()
方法将列表中的元素按照奇偶进行分组。最后,我们输出分区和分组的结果。
通过使用partitioningBy()
和groupingBy()
方法,我们可以方便地对流中的元素进行分区和分组操作,实现对数据的分类和归类。
4.4.4 接合(joining
)
在Java Stream API中,joining()
是一个终止操作方法,用于将流中的元素连接成一个字符串。
joining()
方法没有参数,它返回一个包含流中所有元素连接后的字符串。默认情况下,元素之间使用空字符串作为分隔符进行连接,但也可以通过提供自定义的分隔符来指定连接时使用的分隔符。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamJoiningExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World", "Java");
// 使用joining()方法将字符串列表中的元素连接成一个字符串
String result = words.stream()
.collect(Collectors.joining());
// 输出结果
System.out.println(result); // HelloWorldJava
// 使用自定义分隔符连接字符串列表中的元素
String customResult = words.stream()
.collect(Collectors.joining(", "));
// 输出结果
System.out.println(customResult); // Hello, World, Java
}
}
在上面的示例中,我们首先创建了一个包含字符串的列表。然后,我们使用joining()
方法将列表中的元素连接成一个字符串,默认使用空字符串作为分隔符。接着,我们使用自定义的分隔符" ,
"来连接字符串列表中的元素。最后,我们输出连接后的结果。
通过使用joining()
方法,我们可以方便地将流中的元素连接成一个字符串,实现对数据的合并和拼接操作。
4.4.5 归约(reducing
)
在Java Stream API中,reducing()
是一个终止操作方法,用于将流中的元素按照指定的规约操作进行合并,返回一个最终的结果。
reducing()
方法接受一个初始值和一个BinaryOperator
函数式接口作为参数,该接口定义了一个二元操作,用于将两个元素进行合并。它还可以接受一个Function
函数式接口,用于将流中的元素转换为另一种类型。
下面是reducing()
方法的语法:
Optional<T> reducing(BinaryOperator<T> accumulator)
T reducing(T identity, BinaryOperator<T> accumulator)
<U> U reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> accumulator)
其中,T
表示流中的元素类型,U
表示结果的类型,accumulator
表示用于合并元素的操作,identity
表示初始值,mapper
表示元素转换的函数。
reducing()
方法的工作原理如下:
- 对于流中的第一个元素,将其作为累加器的初始值。
- 对于后续的每个元素,使用累加器和当前元素进行合并操作,得到一个新的累加器值。
- 最终返回合并后的累加器值。
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamReducingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用reducing()方法对整数列表进行求和
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum);
// 输出结果
System.out.println("Sum: " + sum.orElse(0));
}
}
在上面的示例中,我们首先创建了一个包含整数的列表。然后,我们使用reduce()
方法对整数列表进行求和,通过传入的Integer::sum
方法作为累加器进行合并操作。最后,我们使用orElse()
方法获取求和结果,并输出结果。
通过使用reducing()
方法,我们可以对流中的元素进行合并操作,实现对数据的聚合和规约。需要注意的是,reduce()
方法返回的是Optional
类型的结果,因为流中可能为空。