Java8学习笔记之Stream API

Stream是Java8引入的一个重度使用lambda表达式的API。
Stream可以用流的方式处理数据集合,在Java8之前,我们处理这些集合,是需要迭代器的,比如iterator,这是外部迭代;而Stream是内部迭代,我们不用关心集合内部元素是如何迭代的,计算机会自动帮我们选择最适合的实现方式。

如何创建一个流

  1. 最常见的,有一个集合对象List<String> strs = Arrays.asList("Java 8 ", "Lambdas ", "In ", "Action");,直接调用strs.stream()就得到一个Stream<String>的流。
    如果想使用并行流增加性能,请使用strs.parallelStream(),或strs.stream().parallel()
  2. 由值创建:Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
  3. 由数组创建:
        int[] numbers = {2, 3, 5, 7, 11, 13};
        int sum = Arrays.stream(numbers).sum();
  1. 由文件创建:
        // 统计文本文件中有多少个不同的单词
        long uniqueWords = 0;
        try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
            uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
                    .distinct()
                    .count();
        } catch (IOException e) {
        }
  1. 由函数生成流:Stream.iterate()Stream.generate()可以生产无限流,即元素有无穷多个。一般来说,应该使用limit(n)来对这种流加以限制,以避免产生无穷多个元素。
    public void iterator() {
        Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
    }

    public void generate() {
        Stream.generate(Math::random).limit(5).forEach(System.out::println);
    }

Stream常用方法

Stream API 支持两种类型的操作:中间操作(如filter或map)和终端操作(如count、findFirst、forEach和reduce)。

我用一个筛选菜单的需求作为示例。

准备工作

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;
        }

        @Override
        public String toString() {
            return name;
        }
    }

public enum Type {MEAT, FISH, OTHER}
public List<Dish> init() {
        return Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));
    }

过滤筛选

谓词:返回boolean的函数

  • filter():接受一个谓词,返回符合条件的元素集合
    @Test
    public void filter() {
        List<Dish> menu = init();
        List<Dish> vegetarianMenu = menu.stream()
                .filter(Dish::isVegetarian)
                .collect(Collectors.toList());
        Assert.assertEquals(4, vegetarianMenu.size());
    }
  • distinct():返回集合中各异的元素集合(去重)
    @Test
    public void distinct() {
        List<Integer> numbers = Arrays.asList(5, 1, 2, 1, 3, 3, 2, 4);
        numbers.stream().distinct().forEach(System.out::println);
    }
  • limit():截取流中指定数量的元素,返回一个不超过给定长度的流。如果流是有序的,则最多会返回前n个元素。
    @Test
    public void limit() {
        List<Dish> menu = init();
        menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .forEach(System.out::println);
    }
  • skip():跳过指定数量元素,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
    @Test
    public void skip() {
        List<Dish> menu = init();
        menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .skip(2)
                .forEach(System.out::println);
    }

映射

  • map():接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
    @Test
    public void map() {
        List<Dish> menu = init();
        List<String> dishNames = menu.stream().map(m -> m.getName()).collect(Collectors.toList());
    }
  • flatMap():一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流,即扁平化为一个流。
    @Test
    public void flatMap() {
        String[] arrayOfWords = {"Goodbye", "World"};
        List<String> words = Arrays.asList(arrayOfWords);
        words.stream()
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .forEach(System.out::println);
    }

上面例子中,split()得到的是String[] 而不是String,因此各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,变为一个流。

匹配

匹配比较简单,返回一个boolean

  • anyMatch():至少匹配一个
  • allMatch():全部匹配
  • noneMatch():全部不匹配,和allMatch相反
    @Test
    public void anyMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().anyMatch(Dish::isVegetarian));
    }

    @Test
    public void allMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().allMatch(d -> d.getCalories() < 1000));
    }

    @Test
    public void noneMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().noneMatch(d -> d.getCalories() >= 1000));
    }

查找

查找有2个方法:findFirst()findAny(),返回一个Optional<T>集合。
如果你不关心返回的元素是哪个,请使用findAny(),因为它在使用并行流时限制较少。

    @Test
    public void findFirst() {
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findFirst(); // 9
        System.out.println(firstSquareDivisibleByThree.get());
    }

    @Test
    public void findAny() {
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findAny(); // 9
        System.out.println(firstSquareDivisibleByThree.get());
    }

归约

归约在汇总结合内所有数据的时候使用。比如求 max,min,sum。

    @Test
    public void reduce() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int sum = numbers.stream().reduce(0, Integer::sum);
        System.out.println(sum);
    }

原始类型流特化

流在内部迭代的过程中,对基本类型会自动装箱和拆箱。为了避免不需要的装箱拆箱,Java8提供了IntStreamDoubleStreamLongStream

  • 普通流转特化流:mapToInt(), mapToLong(), mapToDouble()
  • 特化流转普通流:boxed()
    public void boxedStream() {
        List<Dish> menu = init();
        // 特化
        IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
        // 转回普通Stream
        Stream<Integer> stream = intStream.boxed();
    }

Java 8引入了两个可以用于IntStream和LongStream的静态方法,用于生成给定范围的数字流:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 前言: 讲Stream之前,先来用个小需求带入本文。毕竟代码看的最清楚。 正文: 项目某个页面有个需求,将关键词和...
    T9的第三个三角阅读 2,388评论 1 9
  • 了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...
    龙历旗阅读 3,301评论 3 4
  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    谁在烽烟彼岸阅读 884评论 0 4
  • "独行独坐,独唱独酬还独卧。伫立神伤,无奈轻寒著摸人。"每读至此,都仿若看到内心孤闷难遣而导致焦灼无宁的情状...
    浅呼吸girl阅读 519评论 0 5