- 一个数据源(如集合)来生成流;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果。
1. 数据源构造流
1.1 由集合生成流
集合的Stream()方法获取一个Stream流
List<String> list = new ArrayList<>();
Stream stream = list.stream();
1.2 由数组生成流
你可以使用静态方法Arrays.stream从数组创建一个流,将原始类型的int数组转化为一个IntStream流对其求和
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
1.3 由文件生成流
java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。你可以用这个方法看看一个文件中有多少各不相同的词:
long uniqueWords = 0;
try(Stream<String> lines =Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}
catch(IOException e){
}
1.4 由函数生成流
Stream API提供了两个静态方法来从函数生成无限流流:Stream.iterate和Stream.generate这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值,利用IntStream,LongStream的静态方法range,rangeClose可以生成某个数值范围值的原始流
1. 迭代:iterate提供一个初值,利用上一次生成的结果不断迭代生成一个无限流
//生成偶数流
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
/*斐波纳契数列 0, 1, 1,2, 3, 5, 8, 13, 21, 34, 55…数列中开始的两个数字是
0和1,后续的每个数字都是前两个数字之和。用iterate方法生成斐波纳契元组序列中的前20个元素。*/
Stream.iterate(new int[]{0, 1},t -> new int[]{t[1], t[0]+t[1]})
.limit(20)
.forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));
2. 生成:generate不是依次对每个新生成的值应用函数的。它接受一个Supplier<T>类型的Lambda提供新的值
//生成随机数流
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
3. 数值范围,利用IntStream,LongStream的静态方法rangeClosed方法来生成1到100之间的所有数字如果改用IntStream.range(1, 100),则结果将会是49个偶数,因为range是不包含结束值的
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
2 中间操作
2.1 筛选
filter
distinct
limit
skip
2.2 映射
map
flatMap 流的扁平化,将多个流合并成一个流
对于一张单词表,如何返回一张列表,列出里面 各不相同的字符 呢?例如,给定单词列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]。
第一个版本可能是这样的:
words.stream()
.map(word -> word.split(" "))
.distinct()
.collect(toList());
传递给map方法的Lambda为每个单词返回了一个String[](String列表)。因此,map返回的流实际上是Stream<String[]>类型的。
尝试使用map和Arrays.stream()
words.stream()
.map(word -> word.split(" "))
.map(Arrays::stream)
.distinct()
.collect(toList());
当前的解决方案仍然搞不定!这是因为,你现在得到的是一个流的列表(更准确地说是Stream<String>)
使用flatMap,你可以像下面这样使用flatMap来解决这个问题:
List<String> uniqueCharacters = words.stream()
.map(w -> w.split(" "))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
各个数组并不是分别映射成一个流,而是映射成流的内容。所
有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
2.3 排序
sorted
3 终端操作
3.1 遍历消费
forEach
3.2 查找和匹配
allMatch和anyMatch和noneMatch
findFirst和findAny
3.3 归约
reduce归约操作,将流中元素归约为一个值
//有初始值,传入一个初始值和一个Lambda表达式,不断归约
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);
int product = numbers.stream().reduce(1, (a, b) -> a * b);
//无初始值,考虑流中没有任何元素的情况返回一个Optional对象
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
3.4 收集
collect 收集器是一种高级归约,使用收集器已实现的归约方法,collect收集器只需传入一个收集器即可,Collectors实用类提供了很多静态工厂方法,可以方便地创建常见收集器的实例。而实现Collector接口中即实现了一个自定义收集器,其实现决定了如何对流执行归约操作。
最直接和最常用的收集器是toList等静态方法,它会把流中所有的元素收集到一个List中:但其list是抽象类,若要具体的集合,则需要使用toCollection(xxx::new)
List<Transaction> transactions = transactionStream.collect(Collectors.toList());
Set<Transaction> transactions = transactionStream.collect(Collectors.toSet());
List<Transaction> transactions = transactionStream.collect(Collectors.toCollection(ArrayList::new));
Set<Transaction> transactions =transactionStream.collect(Collectors.toCollection(HashSet::new));
将数据归约汇总为一个值,计数,求最值,求和,求平均值等等
long howManyDishes = menu.stream().collect(Collectors.counting());
long howManyDishes = menu.stream().count();
Comparator<Dish> dishCaloriesComparator =
Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream()
.collect(Collectors.maxBy(dishCaloriesComparator));
Optional<Dish> mostCalorieDish = menu.stream()
.max(dishCaloriesComparator);
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
通过一次summarizing操作你可以就数出菜单中元素的个数,并得到菜肴热量总和、平均值、最大值和最小值:这个收集器会把所有这些信息收集到一个叫作IntSummaryStatistics的类里,它提供了方便的取值(getter)方法来访问结果。打印menuStatisticobject会得到以下输出:
IntSummaryStatistics menuStatistics =menu.stream()
.collect(summarizingInt(Dish::getCalories));
IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800}
连接字符串,joining工厂方法有一个重载版本可以接受元素之间的分界符
String shortMenu = menu.stream().map(Dish::getName).collect(joining());
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
使用收集器分组,如何分组决定于分组函数
Map<Dish.Type, List<Dish>> dishesByType =menu.stream().collect(groupingBy(Dish::getType));
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return
CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
多级分组,双参数版本的Collectors.groupingBy工厂方法创建的收集器,它除了普通的分类函数之外,还可以接受collector类型的第二个参数,单参数groupingBy(f)(其中f是分类函数)实际上是groupingBy(f,toList())的简便写法
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} )
)
);
//按子组收集数据,要数一数菜单中每类菜有多少个,可以传递counting等收集器作为,groupingBy收集器的第二个参数即可按子组收集数据:
Map<Dish.Type, Long> typesCount = menu.stream().collect(
groupingBy(Dish::getType, counting()));
Map<Dish.Type, Optional<Dish>> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories))));
//把收集器的结果转换为另一种类型,因为分组操作的Map结果中的每个值上包装的Optional没什么用,所以你可能想要把它们去掉,把收集器返回的结果转换为另一种类型,你可以使用Collectors.collectingAndThen工厂方法返回的收集器
Map<Dish.Type, Dish> mostCaloricByType = menu.stream()
.collect(groupingBy(Dish::getType,collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),Optional::get)));
网友评论