Java8-流

目录

  • 简介
  • 用法
  • 例子
  • 注意点

一. 简介

流是Java8引入的一个新特性,提供了对集合元素一系列便捷的操作,可以用很少的代码实现复杂的功能。
流有两大类,分别是对象流(Stream),基本数据流(IntStream、LongStream、DoubleStream)。

二.用法

流的使用通常为三个步骤:①创建流 ②描述流 ③求值

创建流

通过Stream静态工厂方法来创建,Stream类提供了以下方法创建流。

  1. of:
    Stream stream = Stream.of(1, 2, 3, 4);
    IntStream intStream = IntStream.of(new int[]{1, 2, 3});
  2. generator:生成一个无限长度的Stream
    可以看作是个工厂,每次返回一个给定类型的对象。
    Stream generate = Stream.generate(Math::random);
  3. iterate:生成无限长度的Stream
    其元素的生成是重复对给定的种子值(seed)调用用户指定函数生成的
    Stream stream = Stream.iterate(1, element -> element + 1);
  4. empty():生成空流
    Stream.empty();
  5. 通过文件生成流:File.lines()
    Stream lines = Files.lines(Paths.get("data.txt"));
  6. 最常用的 通过Collection子类获取Stream
    List list = new ArrayList<>();
    Stream stream = list.stream();
  7. 通过Arrays.stream()封装数据生成流,也很常用
    IntStream intStream = Arrays.stream(new int[]{1, 2, 3});
  8. 比较少用的 CharSequence 接口方法 .chars()
    String str = "hello world";
    IntStream intStream = str.chars();

描述流

创建好流之后,就可以描述流了。更准确的说法是,声明流的中间操作。
中间操作比较常用的有以下几种(返回值为Stream的均为中间操作)

  • .filter(man -> man.getAge() > 18)  过滤器
  • .map(Man::getName)       获取字段,map操作可以执行多次,将数据转为我们期望的
  • .mapToInt            转为数值流,这个和sum这些组合使用可以省去装箱成本
  • .limit(3)            截断流,前三个
  • .distinct()            去重
  • .sorted()            排序
  • .skip(3)             丢弃前三个
  • .flatMap(Arrays::stream)     合并流

求值

与中间操作相对应的是终端操作,也就是我们的第三步,求值。
常用的终端操作有以下几种(返回值不为Stream的为终端操作)

  • .collect(toList());    结果转为List格式
  • .collect(toSet());    结构转为Set格式,自带distinct效果
  • .count();       计数
  • .findfirst();       找第一个
  • .findAny();      找任何一个
  • .forEach(System.out::println) 为每个元素进行的操作
  • .min         获取最小值
  • .max         获取最大值

说明

流是使用了内部迭代而不是外部迭代
内部迭代使用了惰性求值,只有在需要结果的那一刻才进行求值。
所有的中间操作(只描述流)都是惰性求值,因为只返回了Stream。

三.例子

// 用户类

public class User {
    private Integer id;
    private String name;
    private Integer age;
    // GET SET Constructor ..
}

// 初始化数据

List userList = new ArrayList() {
    /** */
    private static final long serialVersionUID = 1L;
    {
        add(new User(1, "Luffy",   17)); 
        add(new User(2, "Zoro",    19)); 
        add(new User(3, "Nami",    18)); 
        add(new User(4, "Usopp",   17)); 
        add(new User(5, "Sanji",   19)); 
        add(new User(6, "Chopper", 15));
        add(new User(7, "Robin",   28)); 
        add(new User(8, "FRANKY",  34)); 
        add(new User(9, "BROOK",   88)); 
    }
};

// 例子

@Test
public void test1() {
    // 获取所有年龄大于17的用户
    List users = userList.stream()
            .filter(user -> user.getAge() > 17)
            .collect(Collectors.toList());
    System.out.println(users);
}

// 静态导入Collectors后会更简洁。
// import static java.util.stream.Collectors.*;

@Test
public void test2() {
    // 根据年龄是否大于17岁对集合进行分块
    Map> users = userList.stream()
        .collect(partitioningBy(u -> u.getAge() > 17));
    System.out.println(users);
}

@Test
public void test3() {
    // 按照年龄,将用户分组
    Map> users = userList.stream()
        .collect(groupingBy(User::getAge));
    System.out.println(users);
}

@Test
public void test4() {
    // 获取所有用户的名称
    List names = userList.stream()
        .map(User::getName)
        .collect(toList());
    System.out.println(names);
}

@Test
public void test5() {
    // 获取年龄最大的用户(只返回一个)
    User user = userList.stream()
        .max((u1, u2) -> {
            return u1.getAge() > u2.getAge() ? 1 : -1;
        })
        .get();
    System.out.println(user);
}

@Test
public void test6() {
    // 获取用户年龄总和
    int ageSum = userList.stream()
        .map(User::getAge)
        .reduce(0, (acc, element) -> {
            acc += element;
            return acc;
        });
    System.out.println(ageSum);

    ageSum = userList.stream()
        .map(User::getAge)
        .reduce(0, Integer::sum);
    System.out.println(ageSum);

    ageSum = userList.stream()
        .collect(summingInt(User::getAge));
    System.out.println(ageSum);
}

@Test
public void test7() {
    // 获取组成用户姓名的字母
    Set set = userList.stream()
        .map(User::getName)
        .map(name -> name.split(""))
        .flatMap(Arrays::stream)
        .collect(toSet());
    System.out.println(set);
}

四.注意点

如何进行调试

// 可以利用peek查看流中的数据状态
userList.stream()
    .peek(System.out::println)
    .map(u -> u.getName())
    .peek(System.out::println)
    .collect(toList());

使用并行还是串行

// 将流改为并行流十分简单,只需要将stream方法改为parallelStream
Map> users = userList.parallelStream()                
    .collect(groupingBy(User::getAge));

// 或是使用 parallel() 声明为并行
Stream.iterate(0L, i -> i + 1)
    .parallel()
    .limit(10)
    .reduce(0L, Long::sum);

Java8流的并行属于数据并行,即将数据分为几部分,利用多核CPU对每块数据执行运算,最终汇总数据,得到答案。底层是使用了fork/join框架,fork递归分解问题,然后每段并行执行,最终join合并结果。 因此,使用并行流需要考虑以下几点:

  1. 数据量 : 将问题分解之后并行化处理,再将结果合并会带来额外的开销。
    因此数据足够大,每个数据处理管道花费的时间足够多时,并行化处理才有意义
  2. 将数据对半分解的难易程度 :
    ArrayList、数组、IntStream.range,这些数据结构支持随机读取,也就是说能轻而易举地被任意分解。
    LinkedList,对半分解太难。需要o(n) 时间复杂度
  3. 核的数量 : 单核的机器是没必要并行化的

你可能感兴趣的:(stream,java8,java)