Java---流式编程

集合优化了对象的存储,而流则是对数据的处理

流是一系列与特定存储机制无关的元素,利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。

使用流的一个核心好处是,它使得程序更加短小并且更易理解。同时流还可以同 Lambda 表达式和方法引用(method references)一起使用。

流一个重要的特性是:流是懒加载的,意味着它在绝对必要的情况下才计算。这个特性是我们可以表示非常大(甚至无限)的序列,而不用担心内存的问题。

流的支持

Java 设计者在设计流时面临着这样一个难题:现存的大量类库不仅为 Java 所用,同时也被应用在整个 Java 生态圈数百万行的代码中。如何将一个全新的流的概念融入到现有类库中呢?

Java 8 采用的解决方案是:在接口中添加被 default默认)修饰的方法。通过这种方案,设计者们可以将流式(stream)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。

流的操作有三种:创建流、修改流元素(中间操作)、消费流(终端操作)

流的创建:

流的创建方式有以下这几种方式:

  • Stream.of():需要传入一组元素参数。
public class StreamOf {
    public static void main(String[] args) {
        Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!");
            .forEach(System.out::println);
    }
}
// 中间需要传入一组元素参数
// 代码中传入的是一组字符串元素,forEach() 传入一个方法引用。
  • stream(): 对于集合类,可以直接调用集合类中的 stream()方法:
List<String> list = Arrays.asList("It's a wonderful day for pie!".split(" "))
list.stream()
    .forEach(System.out::print);
  • Stream.generate(): 需要传入一个 Supplier<T> ,Suppier 是一个通用的函数式接口,所以可以配合Lambda 使用。
// Stream.generate() 中间传入的参数 Generator 类实现Supplier 接口,
//所以实现的 get(),Stream.generate(new Generator()) 会自动调用 get() 产生所需要的流元素。
public class Generator implements Supplier<String> {
    Random rand = new Random(47);
    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    public String get() {
        return "" + letters[rand.nextInt(letters.length)];
    }

    public static void main(String[] args) {
        String word = Stream.generate(new Generator())
                            .limit(30)
                            .collect(Collectors.joining());
        System.out.println(word);
    }
}

搭配 lambda 表达式使用:传入与 supplier 接口中 get() 方法签名相同的方法引用

Stream.generate(() -> "duplicate")
              .limit(3)
              .forEach(System.out::println);
// 输出
// “duplicate”
// “duplicate”
// “duplicate”
  • Stream.iterate(): 该函数有两个参数,以种子(第一个参数)开头,并将其传给方法(第二个参数,需要传入一个方法引用)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 iterate()。
基本类型流的生成
  • InStream.range(): 生成整型序列的流,range() 需要传入两个参数,指定一个序列范围。
  • LongStream:生成 Long 序列的流
  • DoubleStream:生成 double 序列的流
public class Ranges {
    public static void main(String[] args) {
      System.out.println(range(10, 20).sum());
    }
}
// sum() :用于将流中的元素相加求和
数组流生成
  • Arrays.stream(数组) :利用 Arrays 类中含有一个名为 stream() 的静态方法将数组转换成为流
Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })
            .forEach(n -> System.out.format("%f ", n));

同时也可以

 Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)

此时Arrays.stream() 的调用有两个额外的参数。第一个参数告诉 stream() 从数组的哪个位置开始选择元素,第二个参数用于告知在哪里停止。

  • splitAsStream() :Java 8 在 java.util.regex.Pattern 中增加的一个新的方法。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 CharSequence,因此不能将流作为 splitAsStream() 的参数。
中间操作

中间操作,用于从一个流中获取对象,并进行处理,再将对象作为另一个流从后端输出,以连接到其他操作。

查看流中元素

peek(): 该操作的目的是帮助调试。它允许你无修改地查看流中的元素。因为 peek() 符合无返回值的 Consumer 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象

排序

sorted() 操作排序,使用默认比较器实现,另一种形式是传入一个比较器。另一种形式的实现:传入一个 Comparator 参数。

移除元素

distinct(): 可用于消除流中的重复元素。相比创建一个 Set 集合,该方法的工作量要少得多。

filter(Predicate):过滤操作会保留与传递进去的过滤器函数计算结果为 true 元素。

isPrime() 作为过滤器函数,用于检测质数。

应用函数到元素
  • map() 会获取流中的所有元素,并且对流中元素应用函数操作从而产生新的元素,并将其传递到后续的流中。

  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream

  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream

  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream

使用 map 也可以产生和接收类型不同的类型
map(类::new) 传入一个类的构造器。可以把接收类型转换为其他类。例如 int 转成一个自定义的类型。

在 flatMap 中组合流

map() 函数的功能是产生流,而如果我们传入的元素是流,则会产生元素流的流。
此时可以利用 flatMap() :该函数功能与 map 一样。但不同是该函数会把流扁平化为元素,最终产生的是元素。

public class FlatMap {
    public static void main(String[] args) {
        Stream.of(1, 2, 3)
        .flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker"))
        .forEach(System.out::println);
    }
}
其他操作:
  • ints(): 方法产生一个流并且 ints() 方法有多种方式的重载 — 两个参数限定了数值产生的边界.
    相应其他数据类型的操作:longs()

  • limit() : 操作获取指定数量的元素
    limit(8):或者前 8 个元素。

  • forEach() 方法遍历输出,它根据传递给它的函数对每个流对象执行操作。
    通常使用该方法传入 System.out::println 来遍历输出流中的每个元素。

  • boxed(): 流操作将会自动地把基本类型包装成为对应的装箱类型, 对应解箱 unboxed()

  • skip() 操作 : 它根据参数丢弃指定数量的流元素

Optional 对象

一些标准流返回 Optional 对象,此对象可以作为流的持有者,即使查看的元素不存在也能友好的提示我们。

流中的一些操作方法

  • findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty

  • findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty

  • max() 和 min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty

class OptionalsFromEmptyStreams {
    public static void main(String[] args) {
        System.out.println(Stream.<String>empty()
             .findFirst());
    }
}
// 输出:Optional.empty

当流为空时,返回 Optional.empty 对象。
空流可以通过 Stream.<String>empty() 创建,也可以运用 Stream.empty(),使用此方法时,需要赋值给一个指定的类型的变量,不然 java 编辑器无法进行类型推导,即

Stream<String> s = Stream.empty()

当接收到 Optional 对象时,应首先调用 isPresent()检查其中是否包含元素。如果存在,可使用 get() 获取。

class OptionalBasics {
    static void test(Optional<String> optString) {
        if(optString.isPresent())
            System.out.println(optString.get()); 
        else
            System.out.println("Nothing inside!");
    }
    public static void main(String[] args) {
        test(Stream.of("Epithets").findFirst());
        test(Stream.<String>empty().findFirst());
    }
}
便利函数

通常对于流返回的 Optional对象,我们需要进行解包,才能获取到里面的元素。
流中提供了一些便利函数,用于解包 Optional 对象,简化了 “对所包含的对象的检查和执行操作”:

  • ifPresent(Consumer):当值存在时调用Consumer,否则什么也不做。
  • orElse(otherObject):如果值存在则直接返回,否则生成 otherObject
  • orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。
  • orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数生成一个异常。
创建 Optional

当需要在代码中添加 Optional对象时,可以利用 Optional对象的静态方法来创建:

  • empty():生成一个空 Optional。
  • of(value):将一个非空值包装到 Optional 里。
  • ofNullable(value):针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional中。
Optional 对象的操作

当流管道生成了 Optional 对象,可以使用下列一些方法对对象进行操作。

  • filter(Predicate):将 Predicate 应用于 Optional 中的内容并返回结果。当 Optional 不满足 Predicate 时返回空。如果 Optional 为空,则直接返回。

  • map(Function):如果 Optional不为空,应用 FunctionOptional 中的内容,并返回结果。否则直接返回 Optional.empty

  • flatMap(Function):同 map(),但是提供的映射函数将结果包装在 Optional 对象中,因此 flatMap() 不会在最后进行任何包装。

一般来说,流的 filter() 会在 Predicate 返回 false 时移除流元素。而Optional.filter()在失败时不会删除 Optional,而是将其保留下来,并转化为空。

终端操作
数组
  • toArray():将流转换成适当类型的数组。
  • toArray(generator):在特殊情况下,生成自定义类型的数组。
循环
  • forEach(Consumer)常见如 System.out::println 作为 Consumer 函数。
  • forEachOrdered(Consumer): 保证 forEach 按照原始流顺序操作
集合
  • collect(Collector):使用 Collector 收集流元素到结果集合中。
  • collect(Supplier, BiConsumer, BiConsumer):同上,第一个参数 Supplier 创建了一个新结果集合,第二个参数 BiConsumer 将下一个元素包含到结果中,第三个参数 BiConsumer 用于将两个值组合起来。

Collectors 还有其他更加复杂的实现,可以通过查看文档了解。
例如:实现将流元素收集到 TreeSet 中的集合。可以利用将集合的的构造函数引用传递给 Collectors.toCollection(),从而构建任何类型的集合

public class TreeSetOfWords {
    public static void
    main(String[] args) throws Exception {
        Set<String> words2 =
                Files.lines(Paths.get("TreeSetOfWords.java"))
                        .flatMap(s -> Arrays.stream(s.split("\\W+")))
                        .filter(s -> !s.matches("\\d+")) // No numbers
                        .map(String::trim)
                        .filter(s -> s.length() > 2)
                        .limit(100)
                        .collect(Collectors.toCollection(TreeSet::new));
        System.out.println(words2);
    }
}

Files.lines() 打开 Path 并将其转换成为行流。

组合
  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional
  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。
  • reduce(identity, BiFunction, BinaryOperator):更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 map() 和 reduce() 来更简单的表达它。
匹配
  • allMatch(Predicate) :如果流的每个元素根据提供的 Predicate 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。
  • anyMatch(Predicate):如果流中的任意一个元素根据提供的 Predicate 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。
  • noneMatch(Predicate):如果流的每个元素根据提供的 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。
查找
  • findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty。
  • findAny():返回含有任意流元素的 Optional,如果流为空返回 Optional.empty。
信息
  • count():流中的元素个数。
    max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。
  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。
数字流信息
  • average():求取流元素平均值。
  • max() 和 min():数值流操作无需 Comparator。
  • sum():对所有流元素进行求和。
  • summaryStatistics():生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。

collect() 收集操作,它根据参数来组合所有流中的元素。
当传入的参数是Collectors.joining(),你将会得到一个 String 类型的结果,每个元素都根据 joining() 的参数来进行分割。其他不同的 Collectors 的参数则会产生不同的结果

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

推荐阅读更多精彩内容

  • 本次课程的标题不像之前那样易懂,是一个陌生的概念,“流式编程”是个什么东西? 在了解流式编程之前先思考一下“流”,...
    tommy990607阅读 13,147评论 1 1
  • Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Ja...
    哈哈大圣阅读 1,925评论 0 3
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,227评论 1 2
  • 00-流式编程思想1 背景 事件数据的产生随着时间的推移逐渐下降 人们对某件事的理解往往来自基于有效论据的结论。要...
    蜗牛写java阅读 830评论 0 1
  • “周周,我要去南京。我已经联系到南京那边的医院了,到时候过去直接任职。”黑暗中,我的声音有些沙哑。 躺在对面床上的...
    阿长青阅读 2,982评论 108 99