第四章:使用流
本章内容:
筛选、切片和匹配
查找、匹配和归约
使用数值范围等数值流
从多个源创建流
无限流
4.1 筛选和切片
4.1.1 用谓词筛选
Stream接口支持filter方法,该方法会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流
Dish.menu.stream().filter(Dish::isVegetarian).collect(toList()); // 方法引用检查菜肴是否为素食
4.1.2 筛选各异的元素
流还支持一个叫distinct的方法,它会返回一个元素各异(根据流所产生元素的hashCode和equals方法实现)的流。例如,下面代码会筛选出列表中所有的偶数,并确保没有重复
List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream().distinct().forEach(System.out::println);
4.1.3 截短流
流支持limit方法,该方法会返回一个不超过给定长度的流。所需要的长度作为参数传递给limit。例如下面代码选出超过300卡路里的头三道菜
List<Dish> limit3 = Dish.menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());
4.1.4 跳过元素
List<Dish> skip2 = Dish.menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
练习:如何筛选前两个荤菜?
List<Dish> meatLimit2 = Dish.menu.stream().filter(d -> d.getType() == Dish.Type.MEAT).limit(2).collect(toList());
4.2 映射
4.2.1 对流中每一个元素应用函数
流支持map方法,它接受一个函数作为参数。这个函数会应用到每个元素上,并将其映射成一个新的元素,下面的代码来提取菜肴的名称
List<String> mapList = Dish.menu.stream().map(Dish::getName).collect(toList());
4.2.2 流的扁平化
如果现在想返回单词里的各不相同的字符
List<String> list = Arrays.asList("Hello", "World");
List<String[]> wordArr = list.stream().map(word -> word.split("")).distinct().collect(toList());
1. Arrays.stream()
Arrays.stream()接受一个数组并产生一个流
String[] array = {"Hello", "World"};
Stream<String> stream = Arrays.stream(array);
List<Stream<String>> strngStream = list.stream().map(word -> word.split("")).map(Arrays::stream).distinct().collect(toList());
2. 使用flatMap
flatMap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
List<String> words = list.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
4.3 查找与匹配
4.3.1 至少匹配一个元素(anyMatch)
Dish.menu.stream().anyMatch(Dish::isVegetarian)
4.3.2 是否匹配所有元素(allMatch)、是否不匹配任何元素(noneMatch)
Dish.menu.stream().allMatch(d -> d.getCalories() < 1000)
Dish.menu.stream().noneMatch(d -> d.getCalories() >= 1000)
4.3.3 查找任意一个元素(findAny)
// Optional类是一个容器类,代表一个值存在或者不存在,用来解决空指针异常。Optional类后面细讲 Optional<Dish> findAny = Dish.menu.stream().filter(Dish::isVegetarian).findAny(); if(findAny.isPresent()) { System.out.println(findAny.get().getName()); }
Dish.menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d -> System.out.println(d.getName()));
4.3.4 查找第一个元素(findFirst)
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5); Optional<Integer> findFirst = someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); if(findFirst.isPresent()) { System.out.println(findFirst.get()); }
4.4 归约
4.4.1 元素求和
Integer sum = someNumbers.stream().reduce(0, (a, b) -> a + b);
reduce接受两个参数:
1. 一个初始值,这里是0
2. 一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的Lambda是 (a, b) -> a + b
如果想把所有元素相乘,只需要将另一个Lambda (a, b) -> a * b传递给reduce就行了
在java8中,Integer类提供了一个静态的sum方法来对两个数求和,那么就可以使用方法引用的写法:
sum = someNumbers.stream().reduce(0, Integer::sum);
无初始值:
reduce还有一个重载的方法,不接受初始值,但是返回一个Optional对象
Optional<Integer> reduce = someNumbers.stream().reduce(Integer::sum);
4.4.2 最大值和最小值
Optional<Integer> max = someNumbers.stream().reduce(Integer::max); if(max.isPresent()) { System.out.println(max.get()); } someNumbers.stream().reduce(Integer::min).ifPresent(min -> System.out.println(min));;
总结:
| 操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
| filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
| distinct | 中间 | Stream<T> | ||
| skip | 中间 | Stream<T> | long | |
| limit | 中间 | Stream<T> | long | |
| map | 中间 | Stream<R> | Function<T, R> | T -> R |
| flatMap | 中间 | Stream<R> | Function<T, Stream<R>> | T -> Stream<R> |
| sorted | 中间 | Stream<T> | Comparator<T> | (T, T) -> int |
| anyMatch | 终端 | boolean | Predicate<T> | T -> boolean |
| noneMatch | 终端 | boolean | Predicate<T> | T -> boolean |
| allMatch | 终端 | boolean | Predicate<T> | T -> boolean |
| findAny | 终端 | Option<T> | ||
| findFirst | 终端 | Option<T> | ||
| forEach | 终端 | void | Consumer<T> | T -> void |
| collect | 终端 | R | Collector<T, A, R> | |
| reduce | 终端 | Option<T> | BinaryOperator<T> | (T, T) -> T |
| count | 终端 | long |
练习:
1. 找出2011年发生的所有交易,并按交易额排序(从低到高)
2. 交易员都在哪些不同的城市工作过
3. 查找所有来自剑桥的交易员,并按姓名排序
4. 返回所有交易员的姓名字符串,按字母顺序排序
5. 有没有交易员是在米兰工作的
6. 打印生活在剑桥的交易员的所有交易额
7. 所有交易中,最高的交易额是多少
8. 找出交易额最小的交易
交易员与交易实体类及数据如下:
// 交易员 public class Trader{ private String name; private String city; public Trader(String n, String c){ this.name = n; this.city = c; } public String getName(){ return this.name; } public String getCity(){ return this.city; } public void setCity(String newCity){ this.city = newCity; } public String toString(){ return "Trader:"+this.name + " in " + this.city; } }
// 交易 public class Transaction{ private Trader trader; private int year; private int value; public Transaction(Trader trader, int year, int value) { this.trader = trader; this.year = year; this.value = value; } public Trader getTrader(){ return this.trader; } public int getYear(){ return this.year; } public int getValue(){ return this.value; } public String toString(){ return "{" + this.trader + ", " + "year: "+this.year+", " + "value:" + this.value +"}"; } }
// 测试数据 Trader raoul = new Trader("Raoul", "Cambridge"); Trader mario = new Trader("Mario","Milan"); Trader alan = new Trader("Alan","Cambridge"); Trader brian = new Trader("Brian","Cambridge"); List<Transaction> transactions = Arrays.asList( new Transaction(brian, 2011, 300), new Transaction(raoul, 2012, 1000), new Transaction(raoul, 2011, 400), new Transaction(mario, 2012, 710), new Transaction(mario, 2012, 700), new Transaction(alan, 2012, 950) );
解答:
1. 找出2011年发生的所有交易,并按交易额排序(从低到高)
List<Transaction> sort1 = transactions.stream().filter(t -> t.getYear() == 2011) .sorted(comparing(Transaction::getValue)) .collect(toList()); // 倒序1 List<Transaction> sort2 = transactions.stream().filter(t -> t.getYear() == 2011) .sorted((t1, t2) -> ((Integer) t2.getValue()).compareTo(t1.getValue())) .collect(toList()); // 倒序2 List<Transaction> sort3 = transactions.stream().filter(t -> t.getYear() == 2011).collect(toList()); Comparator<Transaction> c = (t1, t2) -> ((Integer) t1.getValue()).compareTo(t2.getValue()); sort3.sort(c.reversed());
2. 交易员都在哪些不同的城市工作过
List<String> citys = transactions.stream().map(t -> t.getTrader().getCity()).distinct().collect(toList());
3. 查找所有来自剑桥的交易员,并按姓名排序
List<Trader> names = transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getTrader)
.distinct()
.sorted(comparing(Trader::getName))
.collect(toList());
4. 返回所有交易员的姓名字符串,按字母顺序排序
String nameStr = transactions.stream().map(t -> t.getTrader().getName())
.distinct()
.sorted()
.reduce("", (n1, n2) -> n1 + n2);
// 优化(reduce对字符串做相加的时候效率不高,每次迭代都会创建一个新的String对象,可以用joining(其内部用到StringBuild)),下一章会讲到
nameStr = transactions.stream().map(t -> t.getTrader().getName())
.distinct()
.sorted()
.collect(Collectors.joining());
5. 有没有交易员是在米兰工作的
boolean anyMatch = transactions.stream().anyMatch(t -> t.getTrader().getCity().equals("Milan"));
6. 打印生活在剑桥的交易员的所有交易额
transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge"))
.map(t -> t.getValue())
.forEach(System.out::println);
7. 所有交易中,最高的交易额是多少
Optional<Integer> maxT = transactions.stream().map(Transaction::getValue).reduce(Integer::max);
8. 找出交易额最小的交易
Optional<Transaction> minT = transactions.stream().reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2); // 流也支持min和max方法 minT = transactions.stream().min(comparing(Transaction::getValue));
4.5 数值流
Integer sumCalories = Dish.menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
上面的代码有一个暗含的拆箱成本,每一个Integer都必须拆箱成一个原始类型,再进行求和
4.5.1 原始类型特化
java8引入了三个原始类型特化流的接口:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、double和long,从而避免了暗含的拆箱操作
1. 映射到数值流
int intStreamSum = Dish.menu.stream().mapToInt(Dish::getCalories).sum();
如果流是空的,sum默认返回0。IntStream还支持其他方便的方法,如max、min、average等
2. 转换回对象流
要把原始流转换成一般流(每个int都会装箱成Integer),可以用boxed方法
IntStream intStream = Dish.menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> integerStream = intStream.boxed();
3. 默认值OptionalInt
求和的例子很简单,因为sum有默认值为0,如果让你计算IntStream中的最大元素该怎么办
OptionalInt maxCalories = Dish.menu.stream().mapToInt(Dish::getCalories).max(); int defaultMaxCalories = maxCalories.orElse(1); // 如果没有最大值的话,显式提供一个默认的最大值
4.5.2 数值范围
java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成数值范围:range和rangeClosed。这两个方法都是第一个接受起始值,第二个接受结束值。但range不包含结束值,rangeClosed则包含结束值
IntStream evenNumbers = IntStream.range(1, 100) // 表示[1, 100) .filter(n -> n % 2 == 0);// 一个1到100的偶数流
4.6 构建流
4.6.1 由值创建流
你可以直接用静态方法Stream.of,通过显式值创建一个流
Stream<String> strStream = Stream.of("java8", "in", "action");
strStream.map(String::toUpperCase).forEach(System.out::println);
4.6.2 由数组创建流
你可以使用静态方法Arrays.stream从数组创建一个流
int[] arrNumbers = {1,2,3,4,5,6}; int arrSum = Arrays.stream(arrNumbers).sum();
4.6.3 由函数生成流:无限流
Stream API提供了两个静态方法从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流(没有固定大小的流)
1. 迭代:
Stream.iterate(0, n -> n + 2).forEach(System.out::println);
Stream.iterate(0, n -> n < 100, n -> n + 2).forEach(System.out::println);
2. 生成:
Stream.generate(Math::random).forEach(System.out::println);
备注:
摘自文献:《Java8实战》(中文版)《Java8 in Action》(英文版)
代码(GitHub地址): https://github.com/changlezhong/java8InAction
posted on 2018-06-05 22:12 changlezhong 阅读(251) 评论(0) 收藏 举报
浙公网安备 33010602011771号