函数式编程(四)Stream流使用
一、概述
在使用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()方法判断是否所有元素都小于10boolean allLessThanTen = numbers.stream().allMatch(n -> n < 10);// 使用noneMatch()方法判断是否没有任何元素等于6boolean 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
类型的结果,因为流中可能为空。
相关文章:

函数式编程(四)Stream流使用
一、概述 在使用stream之前,先理解Optional 。 Optional是Java 8引入的一个容器类,用于处理可能为空的值。它提供了一种优雅的方式来处理可能存在或不存在的值,避免了空指针异常。 Optional的主要特点如下: 可能为空ÿ…...

区块链面临六大安全问题 安全测试方案研究迫在眉睫
区块链面临六大安全问题 安全测试方案研究迫在眉睫 近年来,区块链技术逐渐成为热门话题,其应用前景受到各国政府、科研机构和企业公司的高度重视与广泛关注。随着技术的发展,区块链应用与项目层出不穷,但其安全问题不容忽视。近年…...

K8S---kubelet TLS 启动引导
一、引导启动初始化过程(Bootstrap Initialization ) 1、kubeadm 生成一个Token,类似07401b.f395accd246ae52d这种格式,或者自己手动生成2、使用kubectl命令行,生成一个Secret,具体详见认证、授权3、kubelet 进程启动 (begin)4、kubelet 看到自己没有对应的 kubeconfig…...

Android系统修改驱动固定USB摄像头节点绑定前后置摄像头
前言 Android系统中usb摄像头节点会因为摄像头所接的usb口不同或者usb设备识别顺序不一样而出现每次开机生成的video节点不一样的问题。由于客户app调用摄像头时,需要固定摄像头的节点。因此需要针对前面的情况做处理。 方式1:通过摄像头名称固定摄像头节点 --- a/kernel…...

RT-Thread 内核移植
内核移植 内核移植就是将RTT内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步等功能。 移植可分为CPU架构移植和BSP(Board support package,板级支持包)移植两部…...

springboot中entity层、dto层、vo层通俗理解三者的区别
entity:这个类的属性是跟数据库字段一模一样的(驼峰命名),当我们使用MyBatis-Plus的时候经常用得到。 dto:用于后端接收前端返回的数据,一般是post请求,前端会给我们返回一个json对象ÿ…...

TypeScript_队列结构-链表
队列 队列(Queue),它是一种受限的线性表,先进先出(FIFO First In First Out) 受限之处在于它只允许在队列的前端(front)进行删除操作而在队列的后端(rear)进…...

STM32G0 定时器PWM DMA输出驱动WS2812配置 LL库
通过DMA方式输出PWM模拟LED数据信号 优点:不消耗CPU资源 缺点:占用内存较大 STM32CUBEMX配置 定时器配置 定时器通道:TIM3 CH2 分频:0 重装值:79,芯片主频64Mhz,因此PWM输出频率:…...

记录错误:Access denied for user ‘root‘@‘localhost‘ (using password:No) 解决方案
他说我没输入密码,但是我输入了啊??于是,我试了试这儿,password 一改就好了。。。 他原来是是我打的很快,快速生成的。。。。...

python爬虫实战(5)--获取小破站热榜
1. 分析地址 打开小破站热榜首页,查看响应找到如下接口地址 2. 编码 定义请求头 拿到标头 复制粘贴,处理成json 处理请求头代码如下: def format_headers_to_json():f open("data.txt", "r", encoding"utf-8") # 读…...

单目标应用:基于麻雀搜索算法SSA的微电网优化调度MATLAB
一、微网系统运行优化模型 参考文献: [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、麻雀搜索算法简介 麻雀搜索算法 (Sparrow Search Algorithm, SSA) 是一种新型的群智能优化算法,于2020…...

C# easymodbus
库介绍 EasyModbus是用于 .NET 和 Java 平台上的Modbus TCP/UDP/RTU通讯协议库,支持多种编程语言,如C#、VB.NET、Java、C 与更多C#的变体,如Unity、Mono、.NET Core等等。 EasyModbus的Java版本至少需要Java 7,而C#版本兼容 .NE…...

HikariCP源码修改,使其连接池支持Kerberos认证
HikariCP-4.0.3 修改HikariCP源码,使其连接池支持Kerberos认证 修改后的Hikari源码地址:https://github.com/Raray-chuan/HikariCP-4.0.3 Springboot使用hikari连接池并进行Kerberos认证访问Impala的demo地址:https://github.com/Raray-chuan/springboot-kerberos-hikari-im…...

5分钟看明白rust mod use
rust把mod简单的事没说清,一片混乱,似懂非懂. mod语句查找只有一条规则:先找mod名1.rs,没有就我同名文件夹下的mod名1.rs,如果没有,就同名文件夹下的mod名1/mod.rs,再没有就error. 在mod.rs中,pub mod 文件…...

【Java核心知识】ThreadLocal相关知识
ThreadLocal 什么是ThreadLocal ThreadLoacal类可以为每个线程保存一份独有的变量,该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMap,key为变量的对应的ThreadLocal对象,value为变量的值。 在日常使用中&…...

《Python基础教程(第三版)》阅读笔记 1
目录 1 快速上手:基础知识2 列表和元组3 字符串4 字典5 条件、循环及其他6 抽象7 再谈抽象8 异常9 魔法方法、特性和迭代器10 开箱即用 本文参考自《Beginning Python: from novice to professional》,中文版为《Python基础教程(第三版&#…...

坦克400 Hi4-T预售价28.5万元起,越野新能源好理解
8月25日,在以“智享蓉城,驭见未来”为主题的成都国际车展上,坦克品牌越野新能源再启新程,首次以全Hi4-T新能源阵容亮相展台,释放坦克品牌加速布局越野新能源的强烈信号。 Hi4-T架构首款落地车型坦克500 Hi4-T上市至今斩…...

我的Vim学习笔记(不定期更新)
2023年9月3日,周日上午 学到了啥就写啥,不定期更新 目录 字体 文件 标签页 分屏 调用系统命令 字体 设置字体大小 :set guifont字体:h字体大小 例如,:set guifontMonospace:h20 查询当前使用的字体和字体大小 :set guifont? 查看…...

spring boot项目生成容器并运行
一个安静的周末,shigen又睡懒觉了,上次说的拖延症的惩罚来了:早晚各100个健腹轮练习,早上的已经完成了。今天的文章来的有点晚,但是依旧保持质量。 springboot项目生成容器并运行 背景 将springboot项目打包成jar包&…...

Vue之html中特殊符号的展示
Vue之html中特殊符号的展示 在html中使用特殊字符时直接展示会报错,需要使用实体名称或者实体编号才能展示。 最常用的字符实体 显示结果 描述 实体名称 实体编号空格 < 小于号 < &…...

数据结构1 -- leetcode练习
三. 练习 3.1 时间复杂度 用函数 f ( n ) f(n) f(n) 表示算法效率与数据规模的关系,假设每次解决问题需要 1 微秒( 1 0 − 6 10^{-6} 10−6 秒),进行估算: 如果 f ( n ) n 2 f(n) n^2 f(n)n2 那么 1 秒能解决多…...

Java设计模式:四、行为型模式-05:备忘录模式
文章目录 一、定义:备忘录模式二、模拟场景:备忘录模式三、改善代码:备忘录模式3.1 工程结构3.2 备忘录模式模型结构图3.3 备忘录模式定义3.3.1 配置信息类3.3.2 备忘录类3.3.3 记录者类3.3.4 管理员类 3.4 单元测试 四、总结:备忘…...

MongoDB实验——MongoDB配置用户的访问控制
MongoDB 配置用户的访问控制 一、 实验原理 理解admin数据库:安装MongoDB时,会自动创建admin数据库,这是一个特殊数据库,提供了普通数据库没有的功能,例如,有些账户角色赋予用户操作多个数据库的权限&…...

golang逃逸技术分析
“ 申请到栈内存好处:函数返回直接释放,不会引起垃圾回收,对性能没有影响。 申请到堆上面的内存才会引起垃圾回收。 func F() { a : make([]int, 0, 20) b : make([]int, 0, 20000) l : 20 c : make([]int, 0, l)} “ a和b代码一样࿰…...

说说你了解的 Nginx
分析&回答 nginx性能数据 高并发连接: 官方称单节点支持5万并发连接数,实际生产环境能够承受2-3万并发。内存消耗少: 在3万并发连接下,开启10个nginx进程仅消耗150M内存 (15M10150M) 1. 正向、反向代理 所谓“代理”,是指在内网边缘 …...

SpringWeb(SpringMVC)
目录 SpringWeb介绍 搭建 SpringWeb SpringWeb介绍 Spring Web是一个基于 Servlet API 构建的原始 web 框架,用于构建基于MVC模式的Web应用程序。在 web 层框架历经 Strust1,WebWork,Strust2 等诸多产品的历代更选 之后,目前业界普…...

Mysql 语句
数据库管理 SQL语言分类 DDL 数据定义语言,用于创建数据库对象,如库、表、索引等 create 创建 create database/table; 数据库/表 create table 表名 (括号内添加类型和字段);drop 删除 drop database/table; 数据库/表…...

软考高级架构师——6、软件架构设计
像学写文章一样,在学会字、词、句之后,就应上升到段落,就应追求文章的“布局谋 篇”,这就是架构。通俗地讲,软件架构设计就是软件系统的“布局谋篇”。 人们在软件工程实践中,逐步认识到了软件架构的重要性…...

虚拟内存相关笔记
虚拟内存是计算机系统内存管理的一个功能,它允许程序认为它们有比实际物理内存更多的可用内存。它使用硬盘来模拟额外的RAM。当物理内存不足时,操作系统将利用磁盘空间作为虚拟内存来存储数据。这种机制提高了资源的利用率并允许更大、更复杂的应用程序的…...

【linux】定时任务讲解
文章目录 一. 在某时刻只执行一次:at1. 设置定时任务2. 查看和删除定时任务 二. 周期性执行任务:cron1. 启动crond进程2. 编辑定时任务3. 查看和删除4. 用户权限4.1. 黑名单4.2指定用户 三. /etc/crontab的管理 一. 在某时刻只执行一次:at 1…...