2020-06-27

java8 Stream流

1、什么是流
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达而不是临时编写一个实现),此外流还可以透明的并行处理。
下面来看一个例子:

public class Dish {

    private String name;
    private boolean vegetarian;//蔬菜
    private int calories;//卡路里
    private 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 boolean isVegetarian() { 
      return vegetarian; 
   }
     .......

    public enum Type {MEAT, FISH, OTHER}
}

List<Dish> menu = Arrays.asList(
                new Dish("猪肉", false, 800, Dish.Type.MEAT),
                new Dish("牛肉", false, 700, Dish.Type.MEAT),
                new Dish("鸡肉", false, 400, Dish.Type.MEAT),
                new Dish("炸薯条", true, 530, Dish.Type.OTHER),
                new Dish("米饭", true, 350, Dish.Type.OTHER),
                new Dish("水果", true, 120, Dish.Type.OTHER),
                new Dish("披萨", true, 550, Dish.Type.OTHER),
                new Dish("大虾", false, 300, Dish.Type.FISH),
                new Dish("鱼肉", false, 450, Dish.Type.FISH) );


List<Dish> lowCaloricDishes = new ArrayList<>();//将卡路里低于400的菜品放入集合
        for(Dish d: menu){
            if(d.getCalories() < 400){
                lowCaloricDishes.add(d);
            }
        }
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {//按卡路里高低排序
            public int compare(Dish d1, Dish d2){
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
        List<String> lowCaloricDishesName = new ArrayList<>();//将菜名放入集合
        for(Dish d: lowCaloricDishes){
            lowCaloricDishesName.add(d.getName());
        }

 List<String> lowCaloricDishesName =  //java 8
                menu.stream()
                        .filter(d -> d.getCalories() < 400)
                        .sorted(comparing(Dish::getCalories))
                        .map(Dish::getName)
                        .collect(toList());

(1)、代码是以声明性方式写的:说明想要完成什么,而不是说明如何实现一个操作(利用for if 等控制语句).这种方法加上行为参数化,可以让你很轻松的应对变化的需求,你很容易再创建一个代码版本,利用Lambda表达式来筛选高卡路里的菜肴,而用不着去复制粘贴代码
(2)、你可以把几个基础操作连接起来:来表达复杂的数据流水线工作,同时保证代码清晰可读.filter 的结果被传给了 sorted 方法,再传给 map 方法,最后传给 collect 方法。

2、流简介:
流的简短的定义就是:"从支持数据处理操作的源生成的元素序列";
(1)元素序列: 就像集合一样,流提供了一个接口,可以访问特定元素类型的一组有序值.因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度来存储访问元素.但流的目的在于表达计算.
(2)源: 流会使用一个提供数据的源,这些源可以是 数组,集合,或输入输出资源.注意:从有序结合生成的流会保留原有的顺序,由列表生成的流,其元素顺序也与列表一致.
(3)数据处理操作: 流的数据处理功能类似于数据库的操作.以及函数式编程语言的常用操作.如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。
此外,流操作有两个重要的特点。

流水线: 很多流操作本身会返回一个流.这样多个操作就可以连接起来形成一个更大的流水线.流水线操作可以看成对数据源进行数据库式查询.
内部迭代: 与使用迭代器对集合进行显示迭代不同,流的迭代都是在背后进行的.

List<String> lowCaloricDishes =
                //1.从 menu 获得流(菜肴列表),建立操作流水线
                menu.stream()
                        //2.选出高热量菜肴
                        .filter(d -> d.getCalories() > 300)
                        //3.输出菜肴名称
                        .map(Dish::getName)
                        //4.只选择前三个
                        .limit(3)
                        //5.将结果保存在另一个List中
                        .collect(toList());

在本例中,我们显示对menu进行stream操作,得到一个流,数据源是菜肴列表menu,接下来对流进行一系列数据处理操作:filter 、 map 、 limit
和 collect 。除了 collect 之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流水线,于是就可以看作对源的一个查询.
最后collect开始处理流水线,并返回一个结果(collect和别的操作不一样,它返回的不是一个流而是一个list).
在调用collect之前,没有任何结果产生,事实上,根本就没有从menu里选择元素.你可以这么理解:链中的方法调用都在排队等待,直到调用 collect

3、流与集合:
(1)集合是数据结构,它的主要目的是以特定的时间/空间复杂度存储和访问元素,流的主要目的在于计算。集合是每个元素先算出来才能添加到集合中,流则是按需计算。
(2)流属于内部迭代,不像使用Collection接口需要用户去做外部迭代(比如for-each),内部迭代可以透明的并行处理,或者对顺序处理做优化
(3)和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。

4、流操作
可以连起来的操作称为中间操作,关闭流的操作称为终端操作

 List<String> names = menu.stream()
                .filter(d -> d.getCalories() > 300)
                .map(Dish::getName)
                .limit(3)
                .collect(toList());
image.png

(1)诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

List<String> names =
                menu.stream()
                        .filter(d -> {
                            System.out.println("filtering" + d.getName());
                            return d.getCalories() > 300;
                        })
                        .map(d -> {
                            System.out.println("mapping" + d.getName());
                            return d.getName();
                        })
                        .limit(3)
                        .collect(toList());
        System.out.println(names);

        结果:
        filtering 猪肉
        mapping 猪肉
        filtering 牛肉
        mapping 牛肉
        filtering 鸡肉
        mapping 鸡肉
        [pork, beef, chicken]

尽管filter和map是两个独立的操作,但它们合并到同一次遍历中了(我们把这种技术叫作循环
合并)。

(2)终端操作会从流的流水线生成结果,比如collect()、count()、forEach()都是终端操作
在调用collect之前,没有任何结果产生,事实上,根本就没有从menu里选择元素.你可以这么理解:链中的方法调用都在排队等待,直到调用 collect

837EDECD-4B80-4EE4-93C4-92507228E245.png

5、使用流
(1)谓词筛选

 List<Dish> vegetarianMenu = menu.stream()//筛选出是素食的菜肴
                .filter(Dish::isVegetarian)
                .collect(toList());

(2)筛选各异元素

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

(3)截短流

        List<Dish> dishes = menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .collect(toList());

(4)跳过元素

 List<Dish> dishe = menu.stream()
                .filter(d -> d.getCalories() > 300)
                .skip(2)
                .collect(toList());

(5)map(它会接受一个函数作为参数,这个函数会被应用到每个元素上.并将其映射成一个新的元素.)

List<String> dishNames = menu.stream()
                .map(Dish::getName)
                .collect(toList());

(6) flatmap (把两个流合并起来,即扁平化为一个流.)

 String[] words = {"Goodbye", "World"};
        List<String> a = Arrays.stream(words)
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)//Arrays.stream() 的方法可以接受一个数组并产生一个流
                .distinct()
                .collect(Collectors.toList());
        a.forEach(System.out::print);
输出:GodbyeWrl

(7)查找和匹配

 if(menu.stream().anyMatch(Dish::isVegetarian)) {
            System.out.println("The menu is (somewhat) vegetarian friendly!!");
        }

allMatch、noneMatch;

Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();

findFirst

(8)归约(将流反复组合起来,得到一个值比如Integer,这样的查询可被归类为 归约 操作.)

 int sum1 = numbers.stream().reduce(0, (a2, b2) -> a2 + b2);//求和
 int sum2 = numbers.stream().reduce(0, Integer::sum);//Integer的静态方法
 Optional<Integer> sum3 = numbers.stream().reduce((a1, b1) -> (a1 + b1));//不给初始值
 Optional<Integer> max = numbers.stream().reduce(Integer::max);//最大值
 Optional<Integer> min = numbers.stream().reduce(Integer::min);//最小值

(9)数值流

 int cal = menu.stream().map(Dish::getCalories)
                .reduce(0, Integer::sum);//它有一个暗含的拆箱成本。每个 Integer 都必须拆箱成一个原始类型

 int calories = menu.stream()
                .mapToInt(Dish::getCalories)//返回一个InStream
                .sum();
        
 IntStream intStream = menu.stream().mapToInt(Dish::getCalories);//将Stream转换为数值流
Stream<Integer> stream = intStream.boxed();//将数值流转换为Stream
        
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);//一个从1到100的偶数流

(10)创建流的几种方式:

 /*//通过Collection得Stream()方法(串行流)或者 parallelStream()方法(并行流)创建Stream
        List<String> list = Arrays.asList("1","2","3","4","0","222","33");
        Stream<String> stream = list.stream();
        Stream<String> stream1 = list.parallelStream();

        //通过Arrays中得静态方法stream获取数组流
        IntStream stream = Arrays.stream(new int[]{1,2,3});

        //通过Stream类中的of()静态方法获取流
        Stream<String> stream = Stream.of("a","b","c");

        //需要传入一个种子,也就是起始值,然后传入一个一元操作
        Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2);
        //生成(无限产生对象)
        Stream<Double> stream2 = Stream.generate(() -> Math.random());*/

6、收集器(Collector)
Collector会对每一个元素应用一个转换函数(如:toList()),将结果累积在一个数据结构中,从而产生这一过程的最终输出.

 Map<Currency, List<Transaction>> transactionsByCurrencies =
                new HashMap<>();
        for (Transaction transaction : transactions) {
            Currency currency = transaction.getCurrency();
            List<Transaction> transactionsForCurrency =
                    transactionsByCurrencies.get(currency);
            if (transactionsForCurrency == null) {
                transactionsForCurrency = new ArrayList<>();
                transactionsByCurrencies
                        .put(currency, transactionsForCurrency);
            }
            transactionsForCurrency.add(transaction);
        }

Map<Currency, List<Transaction>> transactionsByCurrencies =
                transactions.stream().collect(groupingBy(Transaction::getCurrency));

(1)归约汇总

 long howManyDishes = menu.stream().collect(Collectors.counting());//计算菜单里有多少菜

Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(Comparator.comparingInt(Dish::getCalories)));//最大热量的菜

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));//所有菜的热量  averagingInt

 String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));//拼接字符串

(2)分组(一个常见的数据库操作是根据一个或多个属性对集合中的项目进行分组)


Map<Dish.Type, List<Dish>> dishesByType menu.stream().collect(groupingBy(Dish::getType));
 Map<Dish.Type, Map<CaloricLevel, List<Dish>>> map = menu.stream().collect(
                groupingBy(Dish::getType,
                        groupingBy(dish -> {
                            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                            else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                            else return CaloricLevel.FAT;
                        })
                )
        );

Map<Dish.Type, Long> map = menu.stream()
                .collect(groupingBy(Dish::getType, counting()));

(3)分区(由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函
数。分区函数返回一个布尔值)

Map<Boolean, List<Dish>> partitionedMenu menu.stream().collect(partitioningBy(Dish::isVegetarian));

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