Java 8 新特性:Java 类库的新特性之 Stream类(一)

简介: Java 8 新特性:Java 类库的新特性之 Stream类(一)

注:此文中涉及到的一部分图片为网络图片,若有问题,请联系我将其删除。)


一.Java8对IO/NIO 的改进


Java 8 对 IO/NIO 也做了一些改进,主要包括:


改进了java.nio.charset.Charset 的实现,使编码和解码的效率得以提升;

精简了jre/lib/charsets.jar 包;

优化了 String(byte[],*) 构造方法和 String.getBytes() 方法的性能;

增加了一些新的 IO/NIO 方法,使用这些方法可以从文件或者输入流中获取流(java.util.stream.Stream),通过对流的操作,可以简化文本行处理、目录遍历和文件查找。

新增的 API 如下:

BufferedReader.line() --> 返回文本行的流 Stream<String>

File.lines(Path, Charset) --> 返回文本行的流 Stream<String>

File.list(Path) --> 遍历当前目录下的文件和目录

File.walk(Path, int, FileVisitOption) --> 遍历某一个目录下的所有文件和指定深度的子目录

File.find(Path, int, BiPredicate, FileVisitOption... ) --> 查找相应的文件


eg:用流式操作列出当前目录下的所有文件和目录

Files.list(new File(".").toPath()).forEach(System.out::println);

二.简述Stream


Java 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。


最新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。


接口 Stream 官方英文文档地址:

http://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html


Collectors类 官方英文文档地址:


http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html


三.Stream


1. Stream 的官方描述


A sequence of elements supporting sequential and parallel aggregate operations .


(一个序列的元素支持顺序和并行聚合操作。)


理解:


Stream是元素的集合,这点让Stream看起来用些类似Iterator;


可以支持顺序和并行的对原Stream进行聚合(聚合也可以理解为聚汇、合并)的操作。


Note:


可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就OK了!


eg:获取一个List中,元素不为null的个数。


    //Lists是Guava中的一个工具类
    List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
    nums.stream().filter(num -> num != null).count();

2. 流操作

2.1 流(Stream)操作

在官网上有这样一个示例:使用一个原始的流,以及一个只能用在原始流上的sum()方法。

    int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED)
                                  .mapToInt(b -> b.getWeight())
                                  .sum();

流提供了流畅的API,可以进行数据转换和对结果执行某些操作。流操作既可以是“中间的”也可以是“末端的”。

1)中间的 :


中间的操作保持流打开状态,并允许后续的操作。eg:filter()和map()方法就是中间的操作。


这些操作的返回数据类型是流;它们返回当前的流以便串联更多的操作。


2)末端的 :


末端的操作必须是对流的最终操作。当一个末端操作被调用,流被“消耗”并且不再可用。


eg:sum()方法就是一个末端的操作。


Note:中间的 操作是延迟的(lazy)。只有末端的操作会立即开始流中元素的处理。在那个时刻,不管包含了多少中间的操作,元素会在一个传递中处理(通常,但并不总是。有状态的操作eg:sorted() 和distinct()可能需要对元素的二次传送。)


通常,处理一个流需要以下步骤:


(注:这里的步骤和下面讲到的“使用Stream的基本步骤”道理是一样。只是理解方式不同)


从某个源头获得一个流。

执行一个或更多的中间的操作。

执行一个末端的操作。

Note:可能你想在一个方法中执行所有那些步骤。那样,就需要知道源头和流的属性,而且要可以保证它被正确的使用。你可能不想接受任意的Stream<T>实例作为你的方法的输入,因为它们可能具有你难以处理的特性,比如并行的或无限的。

2.2 流操作的特性


1)有状态的:


有状态的操作给流增加了一些新的属性,如:元素的唯一性,或者元素的最大数量,或者保证元素以排序的方式被处理。这些典型的要比无状态的中间操作代价大。


2)短路:


短路操作潜在的允许对流的操作尽早停止,而不去检查所有的元素。这是对无限流的一个特殊设计的属性;如果对流的操作没有短路,那么代码可能永远也不会终止。


2.3 中间的 操作(API方法)


filter() --> 排除所有与断言不匹配的元素。

map() --> 通过Function对元素执行一对一的转换。

flatMap() --> 通过FlatMapper将每个元素转变为无或更多的元素。

peek() --> 对每个遇到的元素执行一些操作。主要对调试很有用。

distinct() --> 根据.equals行为排除所有重复的元素。这是一个有状态的操作。

sorted() --> 确保流中的元素在后续的操作中,按照比较器(Comparator)决定的顺序访问。这是一个有状态的操作。

limit() --> 保证后续的操作所能看到的最大数量的元素。这是一个有状态的短路的操作。

substream() --> 确保后续的操作只能看到一个范围的(根据index)元素。像不能用于流的String.substring一样。也有两种形式,一种有一个开始索引,一种有一个结束索引。二者都是有状态的操作,有一个结束索引的形式也是一个短路的操作。




2.4 末端的 操作(API方法)


forEach() -->对流中的每个元素执行一些操作。

toArray() --> 将流中的元素倾倒入一个数组。

reduce() --> 通过一个二进制操作将流中的元素合并到一起。

collect() --> 将流中的元素倾倒入某些容器,eg:一个Collection或Map.

min() --> 根据一个比较器找到流中元素的最小值。

max() --> 根据一个比较器找到流中元素的最大值。

count() --> 计算流中元素的数量。

anyMatch() --> 判断流中是否至少有一个元素匹配断言。这是一个短路的操作。

allMatch() --> 判断流中是否每一个元素都匹配断言。这是一个短路的操作。

noneMatch()--> 判断流中是否没有一个元素匹配断言。这是一个短路的操作。

findFirst()--> 查找流中的第一个元素。这是一个短路的操作。

findAny() --> 查找流中的任意元素,可能对某些流要比findFirst代价低。这是一个短路的操作。



3. 使用Stream的基本步骤


1)创建Stream;

2)转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换);

3)对Stream进行聚合(Reduce)操作,获取想要的结果。


(注:官方文档 reduce,也叫fold。“聚合”一词是我自己翻译的;也可以理解为合并。具体怎么译看每个人怎么理解。也有人译成 汇聚。)


eg:


剖析Stream通用语法

image.png

图片就是对于Stream例子的一个解析:原本一条语句被三种颜色的框分割成了三个部分。

红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;

绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;

蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。

过程解析如图:

image.png

4. 创建Stream


最常用的创建Stream有两种途径:


1)通过Stream接口的静态工厂方法(Note:Java8里接口可以带静态方法)。


2)通过Collection接口的默认方法 stream(),把一个Collection对象转换成Stream。


4.1 使用Stream静态方法来创建Stream


有三种方式:


1) of方法: 有两个,一个接受变长参数,一个接受单一值。


static <T> Stream<T>of(T... values)


static <T> Stream<T>of(T t)


eg:

Stream<Integer> integerStream = Stream.of(1, 5, 9, 5);
Stream<String> stringStream = Stream.of("meili");

2) generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)


static <T> Stream<T>generate(Supplier<T> s)


eg:

    //1.普通表现形式
    Stream.generate(new Supplier<Double>() {
       @Override
       public Double get() {
      return Math.random();
       }
    });
    //2.lambda表达式的表现形式
    Stream.generate(() -> Math.random());
    //3.方法引用的表现形式
    Stream.generate(Math::random);

上面三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。

每条语句其实都是生成一个无限长度的Stream,其中值是随机的。

这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的 limit()方法来用。

3) iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环


static <T> Stream<T>iterate(T seed, UnaryOperator<T> f)


eg:

    //先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。
    Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);

Note:此方法需配合 limit()方法 使用 ,不然会无限打印下去。


4.2 通过Collection子类获取Stream


Collection接口有一个stream方法,所以其所有子类都都可以获取对应的Stream对象。从List对象获取其对应的Stream对象。

public interface Collection<E> extends Iterable<E> {
      //其他方法省略
      default Stream<E> stream() {
       return StreamSupport.stream(spliterator(), false);
      }
    }

eg:一个流的最常见方法是从一个collection获取。

Stream<T> stream = collection.stream();


目录
相关文章
|
13天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
36 2
|
14天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
33 3
|
14天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
26 2
|
6天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
11天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
32 3
|
11天前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
19 2
|
8天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
79 38
|
5天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
9天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
25 1
[Java]线程生命周期与线程通信
|
7天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。