java8学习-流的使用

java8学习-流的使用

《java8实战》学习笔记

一、筛选和切片

Streams接口支持filter方法(你现在应该很熟悉了)。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。

有这样一个类:Dish

public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }
    public String getName() {
        return name;
    }
    public boolean isVegetarian() {
        return vegetarian;
    }
    public int getCalories() {
        return calories;
    }
    public Type getType() {
        return type;
    }
    public enum Type { MEAT, FISH, OTHER }
    @Override
    public String toString() {
        return name;
    }

    public static final 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, 400, Dish.Type.FISH),
                    new Dish("salmon", false, 450, Dish.Type.FISH));
}

下面多处示例都会用到该类:

public static void main(String[] args) {
        System.out.println("-----------all Dish-----------");
        List<Dish> menu = Dish.menu;
        menu.forEach(System.out :: println);
        System.out.println("--------vegetarian dish-------");
        menu.stream()
                .filter(Dish::isVegetarian)
                .collect(toList())
                .forEach(System.out :: println);
    }

———–all Dish———–
pork
beef
chicken
french fries
rice
season fruit
pizza
prawns
salmon
——–vegetarian dish——-
french fries
rice
season fruit
pizza

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的 hashCode和equals方法实现)的流。
示例:

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
        .filter(i -> i % 2 == 0)
        .distinct()
        .forEach(System.out::println);

2
4

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递 给limit。如果流是有序的,则最多会返回前n个元素。

menu.stream()
        .filter(dish -> dish.getCalories() > 300)
        .limit(3)
        .forEach(System.out::println);

pork
beef
chicken

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一 个空流。

menu.stream()
        .filter(dish -> dish.getCalories() > 300)
        .skip(2)
        .forEach(System.out::println);

chicken
french fries
rice
pizza
prawns
salmon

二、映射

2.1 对流中每一个元素应用函数

流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映 射成一个新的元素。

 List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
        words.stream()
                .map(String::length)
                .forEach(System.out::print);

6726

示例中,每一个String会调用length方法。

2.1 流的扁平化

flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。

三、查找匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。

  • anyMatch: 方法可以回答“流中是否有一个元素能匹配给定的谓词
  • allMatch: 检查谓词是否匹配所有元素
  • noneMatch: allMatch相对
  • findAny: 返回当前流中的任意元素
  • findFirst:查找第一个元素
    >对于流而言,某些操作(例如allMatch、anyMatch、noneMatch、findFirst和findAny) 不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个 短路操作

四、归约

将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作 (将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。

reduce
接受两个参数:
* 初始值
* 产生新值的函数

int count = menu.stream()
       // 把每个菜对映射为1
        .map(d -> 1)
        // 加起来
        .reduce(0, (a, b) -> a + b);

9

map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名, 因为它很容易并行化。

我们可以通过他来求和、最大最小值等。

但使用流也要注意无状态和有状态操作。

五、数值流

int calories = menu.stream()
                .map(Dish::getCalories)
                .reduce(0, Integer::sum);

这个示例中,我们可以计算所有卡路里总和,但每个Integer都必须拆箱成一个原始类型,再求和。

Java 8引入了三个原始类型特化流接口来解决这个问题。IntStream、DoubleStream和 LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每 个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。 此外还有在必要时再把它们转换回对象流的方法。

修改示例如下:

int calories = menu.stream()
                .mapToInt(Dish::getCalories)
                .reduce(0, Integer::sum);

mapToInt会从每道菜中提取热量(用一个Integer表示),并返回一个IntStream (而不是一个Stream)。然后你就可以调用IntStream接口中定义的sum方法,对卡 路里求和了!请注意,如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如
max、min、average等

当然,数值流也可以转回对象流:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> integerStream = intStream.boxed();// 装箱

数值流求和时,如果没用最大值,默认0,但如何区分0是不是最大值呢?可以OptionalInt定义默认值。

OptionalInt maxCalories = menu.stream()
        .mapToInt(Dish::getCalories)
        .max();
maxCalories.orElse(1);

如果想生产随机数,IntStream和LongStream的静态方法,帮助生成这种范围: range和rangeClosed。
示例:生成1到100的数字流:

IntStream evenNumbers = IntStream.rangeClosed(1, 100)
                .filter(i -> i % 2 == 0);
System.out.println(evenNumbers.count());

50
range和rangeClosed一样,只是它不包含结束值,如果此处使用range,结果就是49

综合示例:生产勾股数

public static void main(String[] args) {
    Stream<double[]> pythagoreanTriples =
            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)
                    );

    pythagoreanTriples.limit(5)
            .forEach(t ->
                    System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

    }

3.0, 4.0, 5.0
5.0, 12.0, 13.0
6.0, 8.0, 10.0
7.0, 24.0, 25.0
8.0, 15.0, 17.0

六、构建流

使用stream方法可以从集合生成流,可以根据数值范围创建数值流。但创建流的方法还有许多,如值序列、数组、文件来创建流,甚至由生成函数来创建无限流。

  • Stream.of:由值创建流
  • Arrays.stream:数组创建流
  • Files.lines:由文件生成流
  • Stream.iterate和Stream.generate:创建无限流

静态方法Stream.of,通过显式值创建一个流:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); 
// 空流
Stream<String> emptyStream = Stream.empty();

//斐波纳契元组序列
Stream.iterate(new int[]{0, 1},
                t -> new int[]{t[1],t[0] + t[1]})
                .limit(10)
                .map(t -> t[0])
                .forEach(System.out::println);

七、总结

  • 可以使用filter、distinct、skip和limit对流做筛选和切片。
  • 你可以使用map和flatMap提取或转换流中的元素。 你可以使用findFirst和findAny方法查找流中的元素。你可以用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词。
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  • educe方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
  • filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  • 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
  • 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
  • 无限流是没有固定大小的流。

CONTENTS