Java8新特性之流式操作


什么是流式操作

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

1.流式操作举例

1.1创建实体类

public class Person {

    private String name;

    private Integer age;

    private Integer score;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public Integer getAge() {

        return age;

    }

    public void setAge(Integer age) {

        this.age = age;

    }

    public Integer getScore() {

        return score;

    }

    public void setScore(Integer score) {

        this.score = score;

    }

    public Person() {

    }

    public Person(String name, Integer age, Integer score) {

        this.name = name;

        this.age = age;

        this.score = score;

    }

    @Override

    public String toString() {

        return "Person{" +

                "name='" + name + '\'' +

                ", age=" + age +

                ", score=" + score +

                '}';

    }

}


1.2 传统的对象初始化方式

public class Program {

    public static void main(String[] args) {

        //使用构造器设置对象信息

//        Person xiaomign = new Person("小明", 28, 90);

        //使用getter、setter方式设置对象信息

        Person xiaoming = new Person();

        xiaoming.setName("小明");

        xiaoming.setAge(18);

        xiaoming.setScore(90);

    }

}

1.3 使用流式操作初始化对象

1.3.1 修改实体类

public class Person {

    private String name;

    private Integer age;

    private Integer score;

    public String getName() {

        return name;

    }

    public Person setName(String name) {

        this.name = name;

        return this;

    }

    public Integer getAge() {

        return age;

    }

    public Person setAge(Integer age) {

        this.age = age;

        return this;

    }

    public Integer getScore() {

        return score;

    }

    public Person setScore(Integer score) {

        this.score = score;

        return this;

    }

    public Person() {

    }

    public Person(String name, Integer age, Integer score) {

        this.name = name;

        this.age = age;

        this.score = score;

    }

    @Override

    public String toString() {

        return "Person{" +

                "name='" + name + '\'' +

                ", age=" + age +

                ", score=" + score +

                '}';

    }

}

1.3.2 使用流式操作

//流式操作

xiaoming.setName("小明").setAge(20).setScore(100);

2.集合的流式操作

集合的流式操作是Java8的一个新特性,流式操作不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,可以有序的获取数据源中的每一个数据,并且可以对这些数据进行一些操作。流式操作的每一个方法的返回值都是这个流的本身

2.1 流式操作的三个步骤

2.1.1 获取数据源:集合、数组

设置数据源

public class Data {

    /**

    * 数据源

    */

    public static ArrayList<Person> getData() {

        ArrayList<Person> list = new ArrayList<Person>();

        list.add(new Person("小明", 18, 100));

        list.add(new Person("小丽", 19, 70));

        list.add(new Person("小王", 22, 85));

        list.add(new Person("小张", 20, 90));

        list.add(new Person("小黑", 21, 95));

        return list;

    }

}

获取数据源的方式

public class Program {

    public static void main(String[] args) {

        // 获取数据源方式1

        Stream stream = Data.getData().stream();

        // 获取数据源方式2

        Stream.of(Data.getData());


        // 获取数据源方式3

        //数据源为数组

    }

}

2.1.2 对数据进行处理的过程:过滤、排序、映射等(中间操作)

中间操作1:filter

使用filter自定义条件过滤数据

// 中间操作1: filter

// filter是一个过滤器,可以自定义一个过滤条件,将流中满足条件的元素保留

// 查找集合中成绩小于80的学生

List<Person> list = Data.getData().stream()

    .filter(ele -> ele.getScore() < 80)

    .collect(Collectors.toList());

System.out.println(list);


中间操作2:distinct

使用distinct实现去重操作

在数据源中添加重复的数据

list.add(new Person("小黑", 21, 95)); //此时list中有两个小黑

在实体类中重写hashCode()和equals()方法

@Override

public boolean equals(Object o) {

    if (this == o) return true;

    if (o == null || getClass() != o.getClass()) return false;

    Person person = (Person) o;

    return Objects.equals(name, person.name) &&

        Objects.equals(age, person.age) &&

        Objects.equals(score, person.score);

}

@Override

public int hashCode() {

    return Objects.hash(name, age, score);

}

去重规则:

先判断对象的hashCode()

如果hashCode()相同再判断equals()

// 中间操作2: distinct

// distinct: 取出集合中不同的元素

// 去重规则:

// 1.先判断对象的hashCode()

// 2.如果hashCode()相同再判断equals()

Data.getData().stream().distinct().forEach(System.out::println);

注意:如果小黑的数据相同却要保存两份,可以在hashCode()方法中返回一个随机数,随机数很小概率会相同,为了确保稳定性,可以将equals()方法改为返回false,这样可以保留两个信息相同的小黑。

中间操作3:sorted

使用sorted()方法以成绩进行升序排序

要求实体类实现Comparable接口并重写方法

// 中间操作3: sorted

// sorted: 对返回的元素进行排序

// sorted(): 要求实体类实现Comparable接口并重写方法

Data.getData().stream().sorted().forEach(System.out::println);


中间操作4:limit

在数据源中取前三个数据

// 中间操作4: limit

// limit: 限制,只取流中前指定位的数据

// 在数据源中取前三个数据

Data.getData().stream().limit(3).forEach(System.out::println);


中间操作5:skip

// 中间操作5: skip

// skip: 跳过

// 跳过前三个元素,取后面剩下的元素

Data.getData().stream().skip(3).forEach(System.out::println);


中间操作6:map

元素映射,用指定的元素替换掉流中的元素

使用map将对象替换为对象的名字

// 中间操作6: map

// map: 元素映射,用指定的元素替换掉流中的元素

// 将流中的Person对象替换位他们的姓名

Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);


2.1.3 对流中数据的整合:转成集合、数量(最终操作)

最终操作1:collect

转换为List

public class Program {

    public static void main(String[] args) {

        // 获取数据源方式1

        Stream<Person> stream = Data.getData().stream();

        // 最终操作1: collect,配合Collectors使用

        // 将集合中的元素转换成List

        List<Person> list = stream.collect(Collectors.toList());

        System.out.println(list);

    }

}

运行结果


转换为set

// 将集合中的元素转换为Set

Set<Person> set = stream.collect(Collectors.toSet());

System.out.println(set);

运行结果


转换为map

// 转换为Map(name为键,score为值)

        // 方式1

//        Map<String, Integer> map = stream.collect(Collectors.toMap(

//                ele -> ele.getName(),

//                ele -> ele.getScore()

//        )); 


        // 方式2       

        Map<String, Integer> map = stream.collect(Collectors.toMap(

                Person::getName,

                Person::getScore

        ));

运行结果


最终操作2:reduce

reduce的思想

比如在计算一个数组中的元素的和时,首先会计算前两个数的和,然后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。


计算数组中数据的和

// 最终操作2: reduce(将数据汇总在一起)

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2);

// 获取到最终的返回值

System.out.println(res.get());

使用reduce计算Person对象中成绩的和

// 计算Person中Score的和

Optional<Person> res = stream.reduce(

    (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())

);

System.out.println(res.get().getScore());

缺点:上面的写法每次都会产生一个临时的对象,产生了不必要的性能损耗

使用reduce计算Person对象中成绩的和(优化)

// 计算Person中Score的和(使用临时变量,减少性能开销)

Person temp = new Person();

Optional<Person> res = stream.reduce(

    (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())

);

System.out.println(res.get().getScore());最终操作3:max和min


使用max找出Person中成绩最高的人

// 最终操作3: max和min

// 需求1: 找到集合中成绩最高的人的信息

Person max = stream.max(

    (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();

System.out.println(max);


使用min找出Person中成绩最低的人

// 需求2: 找到集合中成绩最低的人的信息

Person min = stream.min(

    (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();

System.out.println(min);


最终操作4:matching

使用anyMatch查看集合中是否有成绩高于80的人

// 判断集合中是否包含成绩大于80的学员

boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);

System.out.println(res1);


使用allMatch查看集合中的成绩是否全部高于60

//查看集合中的人的成绩是否全部高于60

boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);

System.out.println(res2);


使用noneMatch查看集合中的人的分数是否不包含80以下的

boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80);

System.out.println(res3);


最终操作5:count

使用count计算元数据中有多少条数据

// 最终操作5: 求元数据中有多少个元素

long count = stream.count();

System.out.println(count);


最终操作6:forEach

使用forEach遍历集合中的元素

// 最终操作6: forEach

// stream.forEach(ele -> System.out.println(ele));

stream.forEach(System.out::println);


最终操作7:findFirst和findAny FindFirst: 获取流中的第一个元素FindAny: 获取流中任意一个元素(并不是随机获取元素)对于串行流,结果等同于findFirst findAny用于并行流中可能会与findFirst一样,也可能不一样

// FindFirst: 获取流中的第一个元素

// FindAny: 获取流中任意一个元素(并不是随机获取元素)

//          对于串行流,结果等同于findFirst

//          findAny用于并行流中可能会与findFirst一样,也可能不一样

System.out.println(Data.getData().parallelStream().findFirst());

System.out.println(Data.getData().stream().findFirst());

System.out.println(Data.getData().parallelStream().findAny());

System.out.println(Data.getData().stream().findAny());

最终操作的注意事项

为什么会被称为最终操作?

Person max = stream.max(

    (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();

Person min = stream.min(

    (ele1, ele2) -> ele1.getScore() - ele2.getScore()

).get();


报错信息表示流正在被处理或者已经被关闭了,如果已经被关闭了再次调用当然会报错,这也是为什么叫最终操作的原因。

3.并行流

3.1 获取并行流的方式

// 并行流

// 获取并行流的两种方式

Data.getData().stream().parallel();

Data.getData().parallelStream();

3.2 并行流与串行流对比

// 串行流: 19920ms

// 并行流: 12204ms

long startTime = System.currentTimeMillis();

//LongStream.rangeClosed(0L, 50000000000L)

//    .reduce(Long::sum);

LongStream.rangeClosed(0L, 50000000000L)

    .parallel()

    .reduce(Long::sum);

long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);

3.3 flatMap

String[] array = {"hello", "world"};

// 需要获取所有字符 List -> h, e, l, l, o, w, o, r, l, d

//        Arrays.stream(array)

//                .map(ele -> ele.split(""))

//                .forEach(ele -> System.out.println(ele.length));

System.out.println(Arrays.stream(array)

                  .map(ele -> ele.split(""))

                  .flatMap(Arrays::stream)

                  .collect(Collectors.toList()));


4.Collectors

Collectors是一个工具类,提供着若干个方法,返回一个Collector接口的实现类对象

4.1 maxBy

​ 通过指定的规则获取流中最大的元素

System.out.println(Data.getData().stream()

                .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));


4.2 minBy

​ 通过指定的规则获取流中最小的元素

System.out.println(Data.getData().stream()

                .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));


4.3 joining

合并,将流中的元素,以字符串的形式拼接起来

// 把Person中的姓名拼成一个字符串

String res1 = Data.getData().stream()

    .map(Person::getName)

    .collect(Collectors.joining());

System.out.println(res1);


String res2 = Data.getData().stream()

    .map(Person::getName)

    .collect(Collectors.joining("-"));

System.out.println(res2);


String res3 = Data.getData().stream()

    .map(Person::getName)

    .collect(Collectors.joining("-", "{", "}"));

System.out.println(res3);


4.4 summingInt

计算int类型的和,将流中的元素映射为int类型的元素进行求和

将Person对象的成绩进行求和

// 将Person对象的成绩进行求和

System.out.println(Data.getData().stream()

                  .collect(Collectors.summingInt(ele -> ele.getScore())));


4.5 averagingInt

计算int类型的平均值

计算不及格学生的平均成绩

System.out.println(Data.getData().stream()

                  .filter(ele -> ele.getScore() < 60)

                  .collect(Collectors.averagingInt(Person::getScore)));

4.6 summarizingInt

将流中的元素映射成int类型的元素,获取这些数据的描述信息

System.out.println(Data.getData().stream()

                  .collect(Collectors.summarizingInt(ele -> ele.getScore())));



想获得 更多的学习资料 在评论下方评论【学习】就可以了哦

链接:https://juejin.im/post/5e15e9fc5188253a9b1bf858

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容