五 Stream API

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API( java.util.stream .*) 。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。


一、什么是Stream?

流(Stream)到底是什么呢:是数据渠道,用于操作数据源(集合、数组等)所生成的元素。集合讲的是数据,流讲的是计算。
需要注意的是:
1.Stream自己不会存储元素
2.Stream不会改变源对象,相反他们会返回一个持有结果的新Stream
3.Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
Stream的操作分三个步骤:
1.创建Stream:通过一个数据源(如集合、数组)获取流
2.中间操作:一个中间操作链,对数据源的数据进行处理
3.终止操作(终端操作):一个终止操作,执行中间操作链并产生结果


二、Stream的创建

2.1 通过Collection系列集合提供的stream()(串行流)或parallelStream()(并行流)
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
2.2 通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);

他还有重载形式,能够处理对应的基本类型的数组:


image.png
2.3 由值创建流:通过Stream类中的静态方法of(),可以接受任意数量的参数
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
2.4 由函数创建:创建无限流

方式一:迭代

Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.forEach(System.out::println);

方式二:生成

Stream.generate(() -> Math.random()).forEach(System.out::println);

三、中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理称为"惰性求值"。
讲解之前我们先创建一个集合,添加员工信息:

    List<Employee> employees = Arrays.asList(
            new Employee("张三", 18, 9999.99),
            new Employee("李四", 58, 5555.99),
            new Employee("王五", 26, 3333.33),
            new Employee("赵六", 36, 6666.66),
            new Employee("田七", 12, 8888.88),
            new Employee("田七", 12, 8888.88),
            new Employee("田七", 12, 8888.88)
    );
3.1 筛选与切片:
3.1.1 filter-接收Lambda,从流中排除某些元素

例:获取年龄超过35岁的员工

    @Test
    public void test1(){
        //中间操作:如果没有终止操作不会执行任何操作
        Stream<Employee> stream = employees.stream()
                .filter((e) -> {//内部迭代:迭代操作由Stream API完成
                    System.out.println("Stream API的中间操作");//如果没有下面的终止操作这里不会打印输出
                    return e.getAge() > 35;
                });

        //终止操作:一次性执行全部内容即"惰性求值"
        stream.forEach(System.out::println);
    }
3.1.2 limit-截断流,使其元素不超过给定数量

例:获取工资超过5000的员工,取结果的前两位

    @Test
    public void test2(){
        //中间操作:不会执行任何操作
        employees.stream()
                .filter((e) -> {//只要找到满足条件的结果后后续的迭代操作将不再继续执行
                    System.out.println("我会输出么");
                    return e.getSalary() > 5000;
                })
                .limit(2)
                .forEach(System.out::println);
    }
3.1.3 skip(n)-跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

例:获取工资超过5000的员工结果并去掉结果的前两位

    @Test
    public void test3(){
        //中间操作:不会执行任何操作
        employees.stream()
                .filter((e) -> {
                    System.out.println("我会输出么");
                    return e.getSalary() > 5000;
                })
                .skip(2)
                .forEach(System.out::println);
    }
3.1.4 distinct-筛选,通过流所生成元素的hashCode()和equals()去除重复元素

例:

    @Test
    public void test4(){
        //中间操作:不会执行任何操作
        employees.stream()
                .filter((e) -> {
                    System.out.println("我会输出么");
                    return e.getSalary() > 5000;
                })
                .distinct()
                .forEach(System.out::println);
    }
3.2 映射
3.2.1 map-接收Lambda,将元素转换成其他形式或提取信息.接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新元素
    @Test
    public void test5(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        //转大写
        list.stream()
            .map((str) -> str.toUpperCase())
            .forEach(System.out::println);

        //提取名字
        employees.stream()
                 .map((Employee::getName))
                 .forEach(System.out::println);
    }
3.2.2 flatMap-接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

例:注意测试类为TestStreamMiddleOperation2

 //解析字符串,将字符串中的字符一个个提取出来放到集合中
    public static Stream<Character> filterCharacter(String str) {
        List<Character> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) {
            list.add(ch);
        }
        return list.stream();
    }
    @Test
    public void test6(){
        List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
        Stream<Stream<Character>> stream = list.stream()
                .map(TestStreamMiddleOperation2::filterCharacter);//{{a,a,a},{b,b,b}}
        stream.forEach((s) -> s.forEach(System.out::println));
        System.out.println("----------------------");
        Stream<Character> sm = list.stream()
            .flatMap(TestStreamMiddleOperation2::filterCharacter);//{a,a,a,b,b,b}
        sm.forEach(System.out::println);
    }
3.3 排序

List<String> list = Arrays.asList("ccc", "aaa", "bbb", "ddd", "eee");

3.3.1 sorted()-自然排序(Comparable)
list.stream()
            .sorted()
            .forEach(System.out::println);
3.3.2 sorted(Comparator com)-定制排序
employees.stream()
                .sorted((e1, e2) -> {
                  if (e1.getAge() == e2.getAge()) {
                      return e1.getName().compareTo(e2.getName());
                  }
                  return Integer.compare(e1.getAge(), e2.getAge());
                 })
                .forEach(System.out::println);

四、终止操作:终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

创建一个表示员工信息的集合并添加数据|:

List<Employee> employees = Arrays.asList(
            new Employee("张三", 18, 9999.99, Employee.Status.FREE),
            new Employee("李四", 58, 5555.99, Employee.Status.BUSY),
            new Employee("王五", 26, 3333.33, Employee.Status.VOCATION),
            new Employee("赵六", 36, 6666.66, Employee.Status.FREE),
            new Employee("田七", 12, 8888.88, Employee.Status.BUSY)
    );

Employee类:

public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;
    private Status status;
    public Employee() {
    }
    public Employee(int id) {
        this.id = id;
    }
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public Employee(String name, int age, double salary, Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }
    public Employee(Integer id, Integer age) {
        this.id = id;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public Status getStatus() {
        return status;
    }
    public void setStatus(Status status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                ", status=" + status +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return id == employee.id &&
                age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name) &&
                status == employee.status;
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, salary, status);
    }
    public enum Status {
        FREE,
        BUSY,
        VOCATION
    }
}
4.1 查找与匹配
allMatch-检查是否匹配所有元素
anyMatch-检查是否至少匹配一个元素
noneMatch-检查是否没有匹配所有元素
findFirst-返回第一个元素
findAny-返回当前流中的任意元素
count-返回流中元素的总个数
max-返回流中最大只
min-返回流中最小值

使用方式:

@Test
    public void test1() {
        boolean b1 = employees.stream()
                .allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1);

        boolean b2 = employees.stream()
                .anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2);

        boolean b3 = employees.stream()
                .noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b3);

        //如果返回的值有可能为空,则会把该值封装到Optional容器中去
        Optional<Employee> optional1 = employees.stream()
//                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .sorted(Comparator.comparing(Employee::getSalary))
                .findFirst();
        System.out.println(optional1.get());

        Optional<Employee> optional2 = employees.parallelStream()
                .filter((e) -> e.getStatus().equals(Employee.Status.FREE))
                .findAny();
        System.out.println(optional2.get());

        Long count = employees.stream().count();
        System.out.println(count);

        Optional<Employee> max = employees.stream()
//                .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        .max(Comparator.comparing(Employee::getSalary));
        System.out.println(max.get());

        Optional<Double> min = employees.stream()
                .map(Employee::getSalary)
                .min(Double::compare);
        System.out.println(min.get());
    }
4.2 规约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator):可以将流中元素反复结合起来得到一个值
    @Test
    public void test2(){
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //计算List集合中数据的和
        //返回Integer:因为有起始值0,返回一定不为空
        Integer sum = list.stream()
            .reduce(0, (x, y) -> x + y);
        System.out.println(sum);
        //求员工薪资总和
        //返回Optional:没有起始值,返回值可能为空(后面会介绍Optional)
        Optional<Double> op = employees.stream()
                .map(Employee::getSalary)
                .reduce(Double::sum);
    }
4.3 收集:collect-将流转换为其它形式.接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
4.3.1 组函数
   @Test
   public void test4(){
       //总数
       Long count = employees.stream()
               .collect(Collectors.counting());
       System.out.println(count);

       //工资平均值
       Double avg = employees.stream()
               .collect(Collectors.averagingDouble(Employee::getSalary));
       System.out.println(avg);

       //工资总和
       Double sum = employees.stream()
               .collect(Collectors.summingDouble(Employee::getSalary));
       System.out.println(sum);

       //工资最大值的员工信息
       Optional<Employee> max = employees.stream()
               .collect(Collectors.maxBy(Comparator.comparing(Employee::getSalary)));
       System.out.println(max.get());

       //最小工资
       Optional<Double> min = employees.stream()
               .map(Employee::getSalary)
               .collect(Collectors.minBy(Double::compare));
       System.out.println(min.get());
   }
4.3.2 分组
    @Test
    public void test5(){
        Map<Employee.Status, List<Employee>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));

        System.out.println(map);
    }
4.3.3 分级分组
    @Test
    public void test6(){
        Map<Employee.Status, Map<String, List<Employee>>> collect = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
                    if (e.getAge() <= 35) {
                        return "青年";
                    } else if (e.getAge() <= 50) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                })));

        System.out.println(collect);
    }
4.3.4 分区:满足条件的一个区,不满足的一个区
    @Test
    public void test7(){
        Map<Boolean, List<Employee>> collect = employees.stream()
                .collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));

        System.out.println(collect);
    }
4.3.5 组函数的另一种方式
    @Test
    public void test8(){
        DoubleSummaryStatistics collect = employees.stream()
                .collect(Collectors.summarizingDouble(Employee::getSalary));

        System.out.println(collect.getMax());
    }
4.3.6 字符串连接
    @Test
    public void test9(){
        String collect1 = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining());

        String collect2 = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(","));

        String collect3 = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.joining(",", "==", "---"));

        System.out.println(collect1);
    }

五、练习

练习一:

public class TestStreamPractice4 {
    List<Employee> employees = Arrays.asList(
            new Employee("张三", 18, 9999.99, Employee.Status.FREE),
            new Employee("李四", 58, 5555.99, Employee.Status.BUSY),
            new Employee("王五", 26, 3333.33, Employee.Status.VOCATION),
            new Employee("赵六", 36, 6666.66, Employee.Status.FREE),
            new Employee("田七", 12, 8888.88, Employee.Status.BUSY)
    );

    /**
     * 1.给定一个数字列表,如何返回一个由每个数的平方构成的列表呢
     * 给定[1,2,3,4,5],返回[1,4,9,16,25]
     */
    @Test
    public void test1() {
        Integer[] nums = new Integer[]{1, 2, 3, 4, 5};
        Arrays.stream(nums)
                .map((x) -> x * x)
                .forEach(System.out::println);
    }

    /**
     * 2.怎样用map和reduce方法数一数流中有多少个Employee呢
     */
    @Test
    public void test2(){
        Optional<Integer> sum = employees.stream()
                .map((e) -> 1)
                .reduce(Integer::sum);
        System.out.println(sum.get());
    }
}

练习二:
给定两个类:
交易员类:

public class Trader {

    private String name;
    private String city;

    public Trader() {
    }

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Trader [name=" + name + ", city=" + city + "]";
    }
}

交易类:

public class Transaction {

    private Trader trader;
    private int year;
    private int value;

    public Transaction() {
    }

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return trader;
    }

    public void setTrader(Trader trader) {
        this.trader = trader;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Transaction [trader=" + trader + ", year=" + year + ", value="
                + value + "]";
    }
}

测试类:

public class TestTransaction {
    
    List<Transaction> transactions = null;
    
    @Before
    public void before(){
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");
        
        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 test1(){
        transactions.stream()
                    .filter((t) -> t.getYear() == 2011)
//                  .sorted((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()))
                    .sorted(Comparator.comparing(Transaction::getValue))
                    .forEach(System.out::println);
    }
    
    //2. 交易员都在哪些不同的城市工作过?
    @Test
    public void test2(){
        transactions.stream()
                    .map((t) -> t.getTrader().getCity())
                    .distinct()
                    .forEach(System.out::println);
    }
    
    //3. 查找所有来自剑桥的交易员,并按姓名排序
    @Test
    public void test3(){
        transactions.stream()
                    .filter((t) -> t.getTrader().getCity().equals("Cambridge"))
                    .map(Transaction::getTrader)
                    .sorted((t1, t2) -> t1.getName().compareTo(t2.getName()))
                    .distinct()
                    .forEach(System.out::println);
    }
    
    //4. 返回所有交易员的姓名字符串,按字母顺序排序
    @Test
    public void test4(){
        transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .sorted()
                    .forEach(System.out::println);
        
        System.out.println("-----------------------------------");
        
        String str = transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .sorted()
                    .reduce("", String::concat);
        
        System.out.println(str);
        
        System.out.println("------------------------------------");
        
        transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .flatMap(TestTransaction::filterCharacter)
                    .sorted((s1, s2) -> s1.compareToIgnoreCase(s2))
                    .forEach(System.out::print);
    }
    
    public static Stream<String> filterCharacter(String str){
        List<String> list = new ArrayList<>();
        
        for (Character ch : str.toCharArray()) {
            list.add(ch.toString());
        }
        
        return list.stream();
    }
    
    //5. 有没有交易员是在米兰工作的?
    @Test
    public void test5(){
        boolean bl = transactions.stream()
                    .anyMatch((t) -> t.getTrader().getCity().equals("Milan"));
        
        System.out.println(bl);
    }
    
    
    //6. 打印生活在剑桥的交易员的所有交易额
    @Test
    public void test6(){
        Optional<Integer> sum = transactions.stream()
                    .filter((e) -> e.getTrader().getCity().equals("Cambridge"))
                    .map(Transaction::getValue)
                    .reduce(Integer::sum);
        
        System.out.println(sum.get());
    }
    
    
    //7. 所有交易中,最高的交易额是多少
    @Test
    public void test7(){
        Optional<Integer> max = transactions.stream()
                    .map((t) -> t.getValue())
                    .max(Integer::compare);
        
        System.out.println(max.get());
    }
    
    //8. 找到交易额最小的交易
    @Test
    public void test8(){
        Optional<Transaction> op = transactions.stream()
                    .min((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()));
        
        System.out.println(op.get());
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335