引入流
Stream API的代码
- 声明性 更简洁,更易读
- 可复合 更灵活
- 可并行 性能更好
流是什么?
- 它允许以声明方式处理数据集合
- 遍历数据集的高级迭代器
- 透明地并行处理
- 简短定义:从支持数据处理操作的源生成的元素序列
- 特点:流水线和内部迭代
返回低热量菜肴的名称
Listmenu = FakeDb.getMenu();List lowCaloricDishesName = // menu.stream() menu.parallelStream() // 并行处理 .filter(d -> d.getCalories() < 400) // 筛选低卡路里 .sorted(comparing(Dish::getCalories)) // 按卡路里排序 .map(Dish::getName) // 提取菜肴名称 .collect(toList()); // 返回listSystem.out.println(lowCaloricDishesName);
流与集合?
- 流只能遍历一次
- 流是内部迭代,集合是外部迭代
流的操作?
-
流的使用:一个数据源,一个中间操作链,一个终端操作
-
流的操作有两类:中间操作与终端操作
使用流
筛选、切片
- 谓词筛选 filter
- 去重 distinct
- 截短流 limit
- 跳过元素 skip
// 筛选前2个素菜FakeDb.getMenu().stream() .filter(d -> d.isVegetarian()) // 这就是谓词筛选 .limit(2) // 截断流,返回前n个元素 .forEach(System.out::println);Arrays.asList(1, 2, 2, 3, 3, 3).stream() .distinct() // 去重 .skip(1) // 跳过元素 .forEach(System.out::println);
映射
- 对每个元素应用函数 map
- 扁平化 flatMap 把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
// 提取菜名FakeDb.getMenu().stream() .map(Dish::getName) .forEach(System.out::println);// 求每个字符串长度Arrays.asList("Java 8", "Lambdas", "In", "Action").stream() .map(String::length) .forEach(System.out::println);
流的扁平化 flatMap
单词列表返回字符列表
// 单词列表返回字符列表 如输入列表["Hello", "World"],输出列表["H", "e", "l", "o", "W", "r", "d"]// 尝试一:不成功Arrays.asList("Hello", "World").stream() .map(w -> w.split("")) // 将每个单词转为字符数组 Stream.distinct() .forEach(System.out::println);
Arrays.stream()可以将数组转换成流
// 尝试二:不成功Arrays.asList("Hello", "World").stream() .map(w -> w.split("")) // 将每个单词转为字符数组 Stream.map(Arrays::stream) // 将每个数组转换成一个单独的流 Stream > .distinct() .forEach(System.out::println);
使用flatMap
Arrays.asList("Hello", "World").stream() .map(w -> w.split("")) // 将每个单词转为字符数组 .flatMap(Arrays::stream)// 将各个生成流扁平化为单个流 Stream// 各个数组并不是分别映射成一个流,而是映射成流的内容 .distinct() .forEach(System.out::println);
flatMap方法让你先把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
flatMap使用举例
// 数字列表的平方列表Arrays.asList(1, 2, 3, 4, 5, 6).stream() .map(n -> n * n) .forEach(System.out::println);System.out.println("----------------------");// 两个数组列表的数对Listnumbers1 = Arrays.asList(1, 2, 3);List numbers2 = Arrays.asList(3, 4);numbers1.stream() .flatMap(i -> numbers2.stream().map(j -> new int[]{i, j})) .forEach(a -> System.out.println(a[0] + "\t" + a[1]));System.out.println("----------------------");// 总和能被3整除的数对numbers1.stream() .flatMap(i -> numbers2.stream() .filter(j -> (i + j) % 3 == 0) .map(j -> new int[]{i, j})) .forEach(a -> System.out.println(a[0] + "\t" + a[1]));
查找和匹配
从单词列表中返回所用到的字符
allMatch/anyMatch/noneMatch/findFirst/findAny
匹配
// 至少匹配一个元素boolean match = FakeDb.getMenu().stream() .anyMatch(Dish::isVegetarian);System.out.println(match);// 匹配所有元素match = FakeDb.getMenu().stream() .allMatch(d -> d.getCalories() > 1000);System.out.println(match);// 所有都不匹配match = FakeDb.getMenu().stream() .noneMatch(d -> d.getCalories() > 1000);System.out.println(match);
查找
Optionaldish = FakeDb.getMenu().stream() .filter(Dish::isVegetarian) .findAny();// 关于返回值 Optional 的用法:System.out.println(dish.isPresent()); // 存在返回true,否则返回falsedish.ifPresent(System.out::println); // 存在就执行代码块System.out.println(dish.get()); // 存在返回值,不存在抛异常Dish defaultDish = new Dish("pork", false, 800, Dish.Type.MEAT);System.out.println(dish.orElse(defaultDish)); // 存在返回值,否则返回默认值// 查找第一个元素Optional dish2 = FakeDb.getMenu().stream() .filter(Dish::isVegetarian) .findFirst();
归约
// 终端操作的返回值// allMatch boolean// forEach void// findAny Optional// collect R eg:List List numbers = Arrays.asList(1, 2, 3, 4, 5);// 元素求和,有初始值的情况int sum = numbers.stream() .reduce(0, Integer::sum);System.out.println(sum);// 元素求和,无初始值Optional sum2 = numbers.stream() .reduce(Integer::sum);System.out.println(sum2.get());// 最大值int maxValue = numbers.stream().reduce(0, Integer::max);System.out.println(maxValue);// 最小值int minValue = numbers.stream().reduce(0, Integer::min);System.out.println(minValue);// 菜单中菜的个数int dishCount = FakeDb.getMenu().stream() .map(d -> 1) .reduce(0, (a, b) -> a + b);System.out.println(dishCount);// 内置countSystem.out.println(FakeDb.getMenu().stream().count());
无状态操作:诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。
有状态操作:从流中排序和删除重复项时都需要知道先前的历史
归约应用举例
ListtList = FakeDb.getTransactions();// (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。List list01 = tList.stream() .filter(t -> t.getYear() == 2011) // 2011年的交易 .sorted(Comparator.comparing(Transaction::getValue)) // 交易额从低到高 .collect(Collectors.toList());System.out.println(list01);// (2) 交易员都在哪些不同的城市工作过?List cityList = tList.stream() .map(t -> t.getTrader().getCity()) // 交易员所在城市 .distinct() // 去重 .collect(Collectors.toList());System.out.println(cityList);// (3) 查找所有来自于剑桥的交易员,并按姓名排序。List list3 = tList.stream() .map(Transaction::getTrader) // 交易员 .filter(t -> "Cambridge".equals(t.getCity())) // 来自剑桥 .distinct() // 去重 .sorted(Comparator.comparing(Trader::getName)) // 按姓名排序 .collect(Collectors.toList());System.out.println(list3);// (4) 返回所有交易员的姓名字符串,按字母顺序排序。String nameStr = tList.stream() .map(t -> t.getTrader().getName()) // 交易员的姓名 .distinct() // 去重 // .sorted(Comparator.comparing(String::toString)) // 排序 .sorted() // 排序,可以简写 // .collect(Collectors.toList()); // .reduce("", (n1, n2) -> n1 + n2 + " "); // 这种写法效率不高 .collect(Collectors.joining(" "));System.out.println(nameStr);// (5) 有没有交易员是在米兰工作的?// Optional optional5 = tList.stream()// .map(Transaction::getTrader)// .map(Trader::getCity)// .filter(city -> "Milan".equals(city))// .findAny();// System.out.println(optional5.isPresent());boolean milanBased = tList.stream() .anyMatch(t -> t.getTrader().getCity().equals("Milan"));System.out.println(milanBased);// (6) 打印生活在剑桥的交易员的所有交易额。// Optional optional6 = tList.stream()// .filter(t -> "Cambridge".equals(t.getTrader().getCity()))// .map(Transaction::getValue)// .reduce((a, b) -> a + b);// System.out.println(optional6.get());tList.stream() .filter(t -> "Cambridge".equals(t.getTrader().getCity())) .map(Transaction::getValue) .forEach(System.out::println);// (7) 所有交易中,最高的交易额是多少?Optional optional7 = tList.stream() .map(Transaction::getValue) .reduce(Integer::max);System.out.println(optional7.get());// (8) 找到交易额最小的交易。Optional optional8 = tList.stream() .map(Transaction::getValue) .reduce(Integer::min);System.out.println(optional8.get());
数值流与对象流
// 计算菜单中的卡路里int calories = FakeDb.getMenu().stream() .map(Dish::getCalories) .reduce(0, Integer::sum);// 它有一个暗含的装箱成本,每个Integer都必须拆箱成一个原始类型,再进行求和System.out.println(calories);// 原始类型流特化// IntStream,DoubleStream,LongStream分别将流中的元素特化为int,long,double,从而避免暗含的装箱成本// 映射到数值流int sum = FakeDb.getMenu().stream() // 返回 Stream.mapToInt(Dish::getCalories) // 返回 IntStream .sum(); // sum,max,min,average// sum,如果流是空的,sum默认返回0System.out.println(sum);// 转换回对象流FakeDb.getMenu().stream() .mapToInt(Dish::getCalories) .boxed(); // 转换为Stream // 默认值OptionalInt OptionalDouble OptionalLong// sum有默认值0,max,min,average没有默认值OptionalInt maxCalories = FakeDb.getMenu().stream() .mapToInt(Dish::getCalories) .max();System.out.println(sum);
数值范围
// [1, 100)IntStream numbers = IntStream.range(1, 100);// [1 100]IntStream numbers2 = IntStream.rangeClosed(1, 100);// 求勾股数IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .filter(b -> Math.sqrt(a*a + b*b )%1==0) .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)})) .forEach(a -> System.out.println(a[0] + ", " + a[1] + ", " + a[2]));// 求勾股数 优化IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)})) .filter(t -> t[2] % 1 == 0) .forEach(a -> System.out.println(a[0] + ", " + a[1] + ", " + a[2]));
生成流
// 由值创建流Stream.of("java 8", "Lambdas", "In", "Action") .map(String::toUpperCase) .forEach(System.out::println);// 由数组创建流int[] numbers = {2, 3, 5, 7, 11, 13};int sum = Arrays.stream(numbers).sum();System.out.println(sum);// 由文件生成流:统计文件中的不同单词数long uniqueWords = 0;try(Streamlines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count();} catch (IOException e) { e.printStackTrace();};System.out.println(uniqueWords);// 由函数生成流 无限流// Stream.iterate// Stream.generateStream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);// 迭代:斐波纳契元组数列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] + ")"));// 生成Stream.generate(Math::random) .limit(5) .forEach(System.out::println);// 生成一个全是1的无限流IntStream.generate(() -> 1).limit(11).forEach(System.out::println);// 生成:斐波纳契元组数列IntStream.generate(new IntSupplier() { private int previous = 0; private int current = 1; @Override public int getAsInt() { int oldPrevious = this.previous; int nextValue = this.previous + this.current; this.previous = this.current; this.current = nextValue; return oldPrevious; }}).limit(11).forEach(System.out::println);
其他,备注
遍历数组取索引值
遍历数组取索引值
// import static java.util.stream.Collectors.toList;String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};IntStream.range(0, names.length) .filter(i -> names[i].length() <= i) .mapToObj(i -> names[i]) .collect(toList()); // List// 方案一:IntStream.range(0, names.length) .forEach(i -> System.out.println(i + ", " + names[i]));// 方案二:AtomicInteger index = new AtomicInteger();List list = Arrays.stream(names) .filter(n -> n.length() <= index.incrementAndGet()) .collect(Collectors.toList());// 方案三:使用GuavaStreams.mapWithIndex(Arrays.stream(names) ,(str, index) -> index + ", " + str) .forEach(System.out::println);// 方案四:未看懂Seq.seq(Stream.of(names)).zipWithIndex() .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) .toList();// 方案五:未看懂LazyFutureStream.of(names) .zipWithIndex() .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1) .toList();// 方案六:int[] idx = {-1};Arrays.stream(names) .forEach(i -> System.out.println(++idx[0] + ", " + names[idx[0]]));
第三方库
Guava
Apache
lambdaj
Joda-Time