第5章今天读了一下午,主要是需要敲的案例比较多,第五章案例基本完整代码如下;测试题的实体类代码在这篇文章最下面
5.1 筛选和切片
见代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DishStreamTest {
private List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
/**
* 5.1.1用谓词筛选:筛选出所有的素菜
*/
@Test
public void test01() {
menu.stream().filter(Dish::isVegetarian).forEach(System.out::println);
}
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
/**
* 5.1.2筛选出不同的元素
*/
@Test
public void test02() {
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
}
/**
* 5.1.3截短流&排序(找出卡路里最高的三个食物)
*/
@Test
public void test03() {
menu.stream().filter(dish -> dish.getCalories()>300).sorted(Comparator.comparing(Dish::getCalories).reversed()).limit(3).forEach(System.out::println);
}
/**
* 5.1.4跳过元素(在5.1.3的基础上跳过卡路里最高的两个食物)
*/
@Test
public void test04() {
menu.stream().filter(dish -> dish.getCalories()>300).sorted(Comparator.comparing(Dish::getCalories).reversed()).skip(2).limit(3).forEach(System.out::println);
}
/**
* 5.1.5(测试题)筛选菜单里前两个荤菜
*/
@Test
public void test05() {
menu.stream().filter(dish->!dish.isVegetarian()).limit(2).forEach(System.out::println);
}
}
5.2 映射
5.2.1 对流中每一个元素应用函数
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)。
使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用
map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
示例见代码:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DishStreamTest {
private List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
/**
* 5.2.1 映射:对流中每一个元素应用函数
* 得到每道菜的名字
*/
@Test
public void test06() {
List<String> collect = menu.stream().map(Dish::getName).collect(toList());
System.out.println("collect = " + collect);
}
/**
* 得到每道菜名的长度
*/
@Test
public void test07() {
List<Integer> collect = menu.stream().map(Dish::getName).map(String::length).collect(toList());
System.out.println("collect = " + collect);
}
/**
* 5.2.2 流的扁平化
* 对于一张单词表,如何返回一张列表,列出里面各不相同的字符
*/
@Test
public void test08() {
List<String> list = Arrays.asList("Hello", "World");
// 错误的写法
List<String[]> collect = list.stream().map(word -> word.split("")).distinct().collect(toList());
System.out.println("collect = " + collect);
// 正确的写法
// flatMap将各个生成流扁平化为单个流
// flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
List<String> collect1 = list.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(toList());
System.out.println("collect1 = " + collect1);
}
/**
* 5.2.2 测试题1
* 给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?
*/
@Test
public void test09() {
Integer[] arr = {1, 2, 3, 4, 5};
List<Integer> integers = Arrays.stream(arr).map(e -> e * e).collect(toList());
System.out.println("integers = " + integers);
}
/**
* 5.2.2 测试题2
* 给定两个数字列表,如何返回所有的数对呢?
* 例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1,3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。
*/
@Test
public void test10() {
Integer[] arr1 = {1, 2, 3};
Integer[] arr2 = {3, 4};
List<Integer> list1 = Arrays.asList(arr1);
List<Integer> list2 = Arrays.asList(arr2);
List<int[]> list = list1.stream().flatMap(a -> list2.stream().map(b -> new int[]{a, b})).collect(toList());
}
/**
* 5.2.3 测试题3
* 给如何扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的。
*/
@Test
public void test11() {
Integer[] arr1 = {1, 2, 3};
Integer[] arr2 = {3, 4};
List<Integer> list1 = Arrays.asList(arr1);
List<Integer> list2 = Arrays.asList(arr2);
List<int[]> collect = list1.stream().flatMap(a -> list2.stream().filter(b -> (a + b) % 3 == 0).map(b -> new int[]{a, b})).collect(toList());
}
}
5.3 查找和匹配
另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。StreamAPI通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DishStreamTest {
private List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
/**
* 5.3 查找和匹配
* 5.3.1 检查谓词是否至少匹配一个元素
* anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。
* 因为anyMatch返回结果为boolean,所以是终端操作
*/
@Test
public void test12() {
System.out.println("菜单中有素食: " + menu.stream().anyMatch(Dish::isVegetarian));
}
/**
* 5.3.2 检查谓词是否匹配所有元素
* allMatch方法
*/
@Test
public void test13() {
System.out.println("菜单中所有食物的卡路里都大于100: " + menu.stream().allMatch(d -> d.getCalories() > 100));
}
/**
* 5.3.2 检查谓词是否不匹配所有元素
* noneMatch方法
*/
@Test
public void test14() {
System.out.println("菜单中不存在卡路里大于1000的食物: " + menu.stream().noneMatch(d -> d.getCalories() > 1000));
}
/**
* anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本。
*/
/**
* 5.3.3 查找元素
* findAny也利用了短路原理,查找到第一条符合条件的值就会返回
*/
@Test
public void test15() {
System.out.println("查找菜单中的任意一道素食: " + menu.stream().filter(Dish::isVegetarian).findAny());
}
/**
* 5.3.3 查找元素
* findFirst
*/
@Test
public void test16() {
System.out.println("查找菜单中的第一道素食: " + menu.stream().filter(Dish::isVegetarian).findFirst());
}
}
5.4 归约
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DishStreamTest {
private List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
/**
* 5.4 归约
*/
/**
* 5.4.1 元素求和
* reduce(初始值, 计算规则)
*/
@Test
public void test17() {
Integer[] arr1 = {1, 2, 3};
List<Integer> list = Arrays.asList(arr1);
Integer reduce = list.stream().reduce(100, Integer::sum);
System.out.println("和 = " + reduce);
Optional<Integer> optional = list.stream().reduce(Integer::sum);
System.out.println("不给初始值的情况的和 = " + optional.get());
//这里因为有Optional的加入,所以并不会报空指针
List<Integer> integerList = new ArrayList<>();
System.out.println("不给初始值且集合为空的情况下会得到 = " + integerList.stream().reduce(Integer::sum).isPresent());
}
/**
* 元素求积
*/
@Test
public void test18() {
Integer[] arr1 = {1, 2, 3};
List<Integer> list = Arrays.asList(arr1);
Integer reduce = list.stream().reduce(1, (a, b) -> a * b);
System.out.println("积 = " + reduce);
}
/**
* 5.4.2 找出最大值/最小值
*/
@Test
public void test19() {
Integer[] arr1 = {1, 2, 3};
List<Integer> list = Arrays.asList(arr1);
System.out.println("MAX = " + list.stream().reduce(Integer::max).get());
}
/**
* 归约测试题
* 怎样用map和reduce方法数一数流中有多少个菜呢?
*/
@Test
public void test20() {
System.out.println("总数 = " + menu.stream().map(dish -> 1).reduce(Integer::sum).get());
}
}
5.5 付诸实践
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class DishStreamTest {
/**
* 5.5 测试题
* (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
* (2) 交易员都在哪些不同的城市工作过?
* (3) 查找所有来自于剑桥的交易员,并按姓名排序。
* (4) 返回所有交易员的姓名字符串,按字母顺序排序。
* (5) 有没有交易员是在米兰工作的?
* (6) 打印生活在剑桥的交易员的所有交易额。
* (7) 所有交易中,最高的交易额是多少?
* (8) 找到交易额最小的交易。
*/
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年发生的所有交易,并按交易额排序(从低到高)。*/
@Test
public void test21() {
List<Transaction> collect = transactions.stream().filter(t -> t.getYear() == 2011).sorted(Comparator.comparing(Transaction::getValue)).collect(toList());
System.out.println("collect = " + collect);
}
/**(2) 交易员都在哪些不同的城市工作过?*/
@Test
public void test22() {
List<String> collect = transactions.stream().map(e -> e.getTrader().getCity()).distinct().collect(toList());
System.out.println("collect = " + collect);
}
/**(3) 查找所有来自于剑桥Cambridge的交易员,并按姓名排序。*/
@Test
public void test23() {
List<Trader> traders = transactions.stream().map(Transaction::getTrader)//得到所有交易员
.filter(t -> t.getCity().equals("Cambridge"))//筛选
.distinct()//去重
.sorted(Comparator.comparing(Trader::getName))//排序
.collect(toList());
System.out.println("traders = " + traders);
}
/**(4) 返回所有交易员的姓名字符串,按字母顺序排序。*/
@Test
public void test24() {
String reduce = transactions.stream().map(t -> t.getTrader().getName()).distinct().sorted().reduce("", (a, b) -> a + b + ",");
System.out.println("reduce = " + reduce);
}
/**(5) 有没有交易员是在米兰工作的?*/
@Test
public void test25() {
Optional<Transaction> optional = transactions.stream().filter(e -> e.getTrader().getCity().equals("Milan")).findAny();
System.out.println("有交易员是在米兰工作 = " + optional.isPresent());
}
/**(6) 打印生活在剑桥的交易员的所有交易额。*/
@Test
public void test26() {
Integer sumValue = transactions.stream().filter(e -> e.getTrader().getCity().equals("Cambridge")).map(Transaction::getValue).reduce(0, Integer::sum);
System.out.println("sumValue = " + sumValue);
}
/**(7) 所有交易中,最高的交易额是多少?*/
@Test
public void test27() {
Integer maxValue = transactions.stream().map(Transaction::getValue).reduce(Integer::max).get();
System.out.println("maxValue = " + maxValue);
}
/**(8) 找到交易额最小的交易。*/
@Test
public void test28() {
Transaction transaction = transactions.stream().min(Comparator.comparing(Transaction::getValue)).get();
System.out.println("transaction = " + transaction);
}
}
5.6 数值流
我们在前面看到了可以使用reduce方法计算流中元素的总和。例如,你可以像下面这样计算菜单的热量:
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
这段代码的问题是,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。要是可以直接像下面这样调用sum方法,岂不是更好?
int calories = menu.stream().map(Dish::getCalories).sum();
但这是不可能的。问题在于map方法会生成一个Stream。虽然流中的元素是Integer类型,但Streams接口没有定义sum方法。为什么没有呢?比方说,你只有一个像menu那样的Stream,把各种菜加起来是没有任何意义的。但不要担心,Stream API还提供了原始类型流特化,专门支持处理数值流的方法。
5.6.1 原始类型流特化
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。
5.6.2 数值范围
Java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
IntStream evenNumbers = IntStream.rangeClosed(1, 100) ←─表示范围[1, 100]
.filter(n -> n % 2 == 0); ←─一个从1到100的偶数流
System.out.println(evenNumbers.count()); ←─从1 到100 有50个偶数
5.7 构建流
5.7.1 由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
5.7.2 由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); ←─总和是41
5.7.3 由文件生成流
Java中用于处理文件等I/O操作的NIOAPI(非阻塞I/O)已更新,以便利用StreamAPI。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
5.7.4 由函数生成流:创建无限流
/**斐波那契*/
Stream.iterate(new int[]{0,1},e->new int[]{e[1],e[0]+e[1]}).map(a->a[0]).limit(10).forEach(System.out::println);
5.8 小结
Streams API可以表达复杂的数据处理查询。常用的流操作总结在表5-1中。
你可以使用filter、distinct、skip和limit对流做筛选和切片。
你可以使用map和flatMap提取或转换流中的元素。
你可以使用findFirst和findAny方法查找流中的元素。 你可以用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词。这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
无限流是没有固定大小的流。
Trader和Transaction类的定义如下:
public class Trader{
private final String name;
private final 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 String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
public class Transaction{
private final Trader trader;
private final int year;
private final 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 +"}";
}
}