Java7 Java8 新特性

Java7 Java8 新特性

Java7 新特性

二进制字面量

二进制使用前缀0b0B,如:0b110010
八进制使用前缀0,如:034
十进制没有前缀,就是普通的字面量,如:231
十六进制使用前缀0x0X,如:0x2A7E

数字_分隔符

如果一个数值比较大,很长,那么可以使用_来分割,比如:1_000_000_000,表示1,000,000,000
只能在数字中间添加_符号,不能在边缘添加及小数点前后添加,在编译过程中,_将被编译器删除。

switch 支持 String 字符串

编译器在编译时先做处理:
1) 只有一个 case,直接转成 if;
2) 只有一个 case 和 default,直接转换为 if…else;
3) 有多个 case,先将 String 转换为 hashCode,然后进行相应的处理,JavaCode 在底层兼容 Java7 以前版本。

在 case 中,允许使用的类型有bytecharshortint枚举常量(JDK1.5)、String(JDK1.7),并且它们都必须为字面常量

我们来通过反编译看一下 switch 是如何支持 String 字符串的:

catch 同时捕获多个异常

如果一个catch语句中捕获了多个异常,那么变量e将自动变为final常量。

多个异常之间使用按位或|操作符连接,如:

泛型的类型推断

在 Java7 之前,我们通常需要这么写:ArrayList<String> list = new ArrayList<String>();
在 Java7 之后,我们可以这么写:ArrayList<String> list = new ArrayList<>();

@SafeVarargs注解

@SafeVarargs在 JDK7 中引入,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。

可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用;简单的说,数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来,因此当把一个泛型存储到数组中时,编译器在编译阶段无法检查数据类型是否匹配,因此会给出警告信息:"[unchecked] Possible heap pollution from parameterized vararg type T"

意思就是:存在可能的堆污染(heap pollution),即如果泛型的真实数据类型无法和参数数组的类型匹配,会导致 ClassCastException 异常,因此当在可变长参数中使用泛型时,编译器都会给出警告信息。

忽略这些异常的方法有:
1) 编译器选项:javac -Xlint:unchecked
2) 使用注解:@SuppressWarnings("unchecked")
3) 使用注解:@SafeVarargs(Java7 新增)

try-with-resources

JDK1.7 之前,读取文件的一般操作为:

为什么在 finally 块中不将 IOException 异常抛出呢?因为在 finally 块中抛出的异常会淹没之前被 catch 的 FileNotFoundException、IOException。因此,我们只能使用这样的复杂且冗余的代码(JDK1.7 前)。

在上面的代码中可以看出,如果 try 块中发生异常(文件不存在、文件读取错误),那么该异常将被传递给调用者;如果 try 块和 finally 块中同时发生异常,那么 finally 块中的异常将被抑制,不会向外传播。

不过,在 Java7 之后,提供了更加简便的方法来简化我们的代码,即使用try-with-resources

这段代码的运行逻辑和之前的是一样的,在调用 f.close() 过程中如果发生异常,那么该异常将被抑制,只有 try 块中的异常才会往外传播。

但是有一点不同,之前的代码中,如果 f.close() 发生异常,那么该异常仅仅就是被打印到屏幕而已,并没有记录下来;而在使用了try-with-resources的代码中,如果 f.close() 发生异常,那么它将被隐式的添加至 catch 块中 e 变量的”被抑制异常数组”,可以通过 Throwable.getSuppressed() 方法获得”被抑制异常数组”。

实际上,try-with-resources 并没有什么神秘的地方,这只不过是一颗语法糖而已,try-with-resources 的实现原理还是依赖于 JDK1.7 中 Throwable 添加的两个方法:
void addSuppressed(Throwable exception):将给定异常对象添加到当前异常对象的抑制异常数组中
Throwable[] getSuppressed():获取当前异常对象的抑制异常对象数组

因此,我们完全可以使用以下程序实现所谓的 try-with-resources 语句:

获取被抑制异常数组的例子:

哪些资源可以使用try-with-resources管理
只要一个类实现了 java.lang.AutoCloseable 接口就可以使用try-with-resources进行资源的自动释放。java.lang.AutoCloseable 接口只有一个方法:void close() throws Exception;同时,java.io.Closeable 接口因继承自 java.lang.AutoCloseable 接口,因此也可以使用try-with-resources释放资源。

try-with-resources 语句后面可以有 finally 块,该 finally 块将在 try-with-resources 执行完毕后执行。很好理解,因为 try-with-resources 中已经隐含了一个 finally 语句块了,因此我们自己添加的 finally 块本质上已经没有 finally 的意义了。

try 块中可以注册多个资源,在离开 try 块时,它们将被按照相反的顺序调用 close() 方法。比如:

Objects 工具类

java.util.Objects工具类,提供了一些非常实用的静态方法

Java8 新特性

Java8 是 Java 语言开发的一个主要版本。Oracle 公司于 2014 年 3 月 18 日发布 Java8 ,主要新特性:Lambda 表达式函数式编程风格Stream API新的日期时间 APIOptional 容器用于解决空指针异常接口默认方法/静态方法

Java8 新增了非常多的特性,主要的几个如下:

  • Lambda 表达式:对匿名内部类的一个改进,基本可以取代内部匿名类,并且性能比匿名类好得多;
  • 方法引用:方法引用其实是随 Lambda 产生的语法糖,与 Lambda 联合使用,可以使语言的构造更紧凑简洁,减少冗余代码;
  • 默认方法:默认方法就是一个在接口里面有了一个实现的方法,使用 default 关键字;
  • Stream API:不同于 java.io 中的 Stream 流,新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中;
  • Date Time API:加强对日期与时间的处理;
  • Optional 类:Optional 类已经成为 Java8 类库的一部分,用来解决空指针异常。

Lambda 表达式

Lambda 表达式实质上是一个内部匿名类的对象;
对于匿名类来说,必须继承一个父类或者实现一个接口,在编译之后会生成一个名为外部类$n.class(n 从 1 开始)的独立字节码文件;
对于 Lambda,也会实现一个接口,这个接口只有一个抽象方法(称为”函数式接口”),但是在编译之后并不会生成单独的字节码文件。
因此,Lambda 不能单独存在,它必定和一个函数式接口@FunctionalInterface相关;函数式接口就是只有一个抽象方法的普通接口,为了明确语义,通常使用@FunctionalInterface注解一个函数式接口。

内部类和 Lambda 表达式的命名规则
1) 成员内部类,包括普通成员内部类、静态成员内部类,外部类名$内部类名
2) 局部内部类,外部类名$n内部类名,n 从 1 开始,每个函数都有不同的 n 值
3) 匿名内部类,外部类名$n,n 从 1 开始
4) Lambda 表达式,外部类名$$Lambda$n,n 从 1 开始

不同于 C/C++,在 Java 中,$美元符也是一个合法的标识符,可以位于标识符的任意位置。

Lambda 的语法
(parameters) -> { statements }

  • parameters:函数的形参,即当前 Lambda 所实现的函数式接口中的函数的形参;
    可以省略形参的数据类型,只写形参变量也是可以的,编译器可以自行推断;如(int n)(n)是一样的;
    当函数只有一个参数,并且没有显式指明形参类型,则可以省略小括号,如n -> { ... }
  • statements:函数体,和普通的函数体一样,可以有任意多条语句;
    如果只有一条语句,那么可以省略花括号{},且该语句的返回值类型必须和所实现函数的返回值类型相兼容;
    如果有多条语句,那么就和普通的匿名类一样,需要有花括号{},返回值类型也必须和所实现函数的返回值类型相兼容。

对比一下匿名类、Lambda 表达式:

Lambda 与匿名类的不同之处:

FunctionalInterface 接口

JDK1.8 之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.util.Comparator

JDK1.8 新增加的函数式接口:
函数式接口所在的包:java.util.function

java.util.function 它包含了很多函数式接口,用来支持 Java 的函数式编程,该包中的函数式接口有:
生产者,即不接收参数
Supplier<T>:get(),不接受参数,返回 T 类型结果;
BooleanSupplier:getAsBoolean(),不接受参数,返回 boolean 结果;
IntSupplier:getAsInt(),不接受参数,返回 int 结果;
LongSupplier:getAsLong(),不接受参数,返回 long 结果;
DoubleSupplier:getAsDouble(),不接受参数,返回 double 结果;

消费者,即不返回结果
Consumer<T>:accept(),接受 1 个参数,不返回结果;
BiConsumer<T, U>:accept(),接受 2 个参数,不返回结果;
IntConsumer:accept(),接受 1 个 int 参数,不返回结果;
LongConsumer:accept(),接受 1 个 long 参数,不返回结果;
DoubleConsumer:accept(),接受 1 个 double 参数,不返回结果;
ObjIntConsumer<T>:accept(),接受 1 个 T 类型参数和 1 个 int 参数,不返回结果;
ObjLongConsumer<T>:accept(),接受 1 个 T 类型参数和 1 个 long 参数,不返回结果;
ObjDoubleConsumer<T>:accept(),接受 1 个 T 类型参数和 1 个 double 参数,不返回结果;

条件谓词,即返回布尔值
Predicate<T>:test(),接受 1 个参数,返回 boolean 结果;
BiPredicate<T, U>:test(),接受 2 个参数,返回 boolean 结果;
IntPredicate:test(),接受 1 个 int 参数,返回 boolean 结果;
LongPredicate:test(),接受 1 个 long 参数,返回 boolean 结果;
DoublePredicate:test(),接受 1 个 double 参数,返回 boolean 结果;

一元操作符
UnaryOperator<T>:apply(),接受 1 个 T 类型参数,返回 T 类型结果;
IntUnaryOperator:applyAsInt(),接受 1 个 int 参数,返回 int 结果;
LongUnaryOperator:applyAsLong(),接受 1 个 long 参数,返回 long 结果;
DoubleUnaryOperator:applyAsDouble(),接受 1 个 double 参数,返回 double 结果;

二元操作符
BinaryOperator<T>:apply(),接受 2 个 T 类型参数,返回 T 类型结果;
IntBinaryOperator:applyAsInt(),接受 2 个 int 参数,返回 int 结果;
LongBinaryOperator:applyAsLong(),接受 2 个 long 参数,返回 long 结果;
DoubleBinaryOperator:applyAsDouble(),接受 2 个 double 参数,返回 double 结果;

普通函数,接收 T 参数,返回 R 结果
Function<T, R>:apply(),接受 1 个 T 类型参数,返回 R 类型结果;
BiFunction<T, U, R>:apply(),接受 2 个参数,返回 1 个结果;
IntFunction<R>:apply(),接受 1 个 int 参数,返回 R 类型结果;
IntToLongFunction:applyAsLong(),接受 1 个 int 参数,返回 long 结果;
IntToDoubleFunction:applyAsDouble(),接受 1 个 int 参数,返回 double 结果;
LongFunction<R>:apply(),接受 1 个 long 参数,返回 R 类型结果;
LongToIntFunction:applyAsInt(),接受 1 个 long 参数,返回 int 结果;
LongToDoubleFunction:applyAsDouble(),接受 1 个 long 参数,返回 double 结果;
DoubleFunction<R>:apply(),接受 1 个 double 参数,返回指定类型的结果;
DoubleToIntFunction:applyAsInt(),接受 1 个 double 参数,返回 int 结果;
DoubleToLongFunction:applyAsLong(),接受 1 个 double 参数,返回 long 结果;
ToIntFunction<T>:applyAsInt(),接受 1 个 T 类型参数,返回 int 结果;
ToIntBiFunction<T, U>:applyAsInt(),接受 1 个 T 类型参数和 1 个 U 类型参数,返回 int 结果;
ToLongFunction<T>:applyAsLong(),接受 1 个 T 类型参数,返回 long 结果;
ToLongBiFunction<T, U>:applyAsLong(),接受 1 个 T 类型参数和 1 个 U 类型参数,返回 long 结果;
ToDoubleFunction<T>:applyAsDouble(),接受 1 个 T 类型参数,返回 double 结果;
ToDoubleBiFunction<T, U>:applyAsDouble(),接受 1 个 T 类型参数和 1 个 U 类型参数,返回 double 结果;

例子:

方法引用

方法引用其实就是 Lambda 表达式的一种简写方式。

在 Java8 中,我们会使用 Lambda 表达式创建匿名方法,但是有时候,我们的 Lambda 表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java8 的方法引用允许我们这样做。

方法引用是一个更加紧凑,易读的 Lambda 表达式,方法引用的操作符是双冒号::。例子:

四种方法引用类型
1) 引用静态方法ClassName::staticMethodName
2) 引用实例方法objectName::methodName(隐式地传入this指针);
3) 引用构造方法ClassName::new
4) 引用任意方法ClassName::methodName(对于实例方法,必须显式地传入this指针);

例子:

简要说明:
1) 实例方法引用:this 指针由编译器隐式传递;
2) 静态方法引用:没有 this 指针,静态方法是与实例无关的方法;
3) 构造方法引用:需要指定类名、构造函数参数;
4) 任意方法引用:引用静态方法没有区别,引用实例方法需要显式传入 this 指针。

什么时候适合使用方法引用
Lambda 表达式的目的仅仅为了调用另一个已存在方法时,适合使用方法引用。

什么时候不适合使用方法引用
当我们需要向 Lambda 表达式传入其它参数时,不适合使用方法引用。

接口默认方法、静态方法

Java8 对接口做了进一步的增强:
1、接口中可以定义 default 关键字修饰的非抽象方法,即:默认方法(或扩展方法);
2、接口中可以定义 static 静态方法。

Java8 允许给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法(也称为默认方法虚拟扩展方法防护方法

在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。
但是请注意,接口中的默认方法不能够重载 Object 中的定义的方法。eg:toString、equals、hashCode。

为函数式接口添加 default 方法、static 方法并不会对其有任何影响,依旧是合法的函数式接口;
因为函数式接口的定义是接口中只有一个抽象方法,只要符合这个条件那么就是函数式接口;
同时,在函数式接口中可以定义 Object 类的相关抽象方法,也不会影响它成为一个函数式接口。

例如,下面这段代码可以正常编译:

默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是说不会强迫实现接口的类实现默认方法。默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的实现类都会通过继承得到这个方法(如果有需要也可以重写这个方法)。

例子:

虽然默认方法很强大,但是使用之前一定要仔细考虑是不是真的需要使用默认方法;因为在层级很复杂的情况下很容易导致逻辑模糊不清甚至产生编译错误!

引入默认方法带来的优缺点
优点:默认方法可以在不破坏代码的前提下扩展原有库的功能,避免了代码冗余;
缺点:从另一个方面来说,这使得接口作为协议,类作为具体实现的界限开始变得有点模糊。

引入默认方法之后,感觉接口和抽象类的界限变得有点模糊,接口和抽象类之间有很多相似的地方:
1、都是抽象类型;
2、都不能被实例化;
3、都可以提供具体的方法实现;
4、都可以不需要子类去实现所有方法。

但是接口和抽象类也有一些不同的地方:
1、抽象类不可以多继承,接口可以多继承;
2、设计理念不同,抽象类表示的是”is-a”关系,接口表示的是”like-a”关系;
3、接口中的变量默认被 public static final 修饰,不能在实现类中修改,而抽象类中的变量则没有此限制。

默认方法带来的多继承命名冲突

接口中的默认方法也可以被子接口重写,如:

接口的静态方法
在 Java8 中,接口除了可以有默认方法,还可以定义静态方法,和普通类的静态方法一样。接口静态方法需要通过接口名.方法名方式来调用,否则发生编译错误。

接口中的静态方法不能被实现类、子接口所继承,比如:

Optional 容器

java.util.Optional为了解决臭名昭著的空指针异常而引入的类,本身是一个容器。Optional 是一个泛型类,为了减少boxing、unboxing带来的性能损失,提供了OptionalIntOptionalLongOptionalDouble

OptionalInt

OptionalLong

OptionalDouble

Stream API

Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)

Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行并行两种模式进行汇聚操作,并行模式能够充分利用多核处理器的优势,并行模式是利用 ForkJoin 框架来拆分任务和加速处理过程的。

通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

什么是 Stream 流
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator

原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;
高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator)单向不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。而和迭代器又不同的是,Stream 可以并行化操作;迭代器只能命令式地串行化操作

顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item;
而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

Stream 的另外一大特点是,数据源本身可以是无限的,通常配合短路操作来使用。

Stream 流的构成
当我们使用一个流的时候,通常包括三个基本步骤:
1) 获取一个数据源(source)
2) 数据转换,中间操作
3) 执行操作获取想要的结果,末端操作

每次转换原有 Stream 对象不改变,而是返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。

流管道(Stream Pipeline)的构成
Stream Pipeline 流管道

生成 Stream 的方式

  • Collection 集合、Array 数组
    • Collection.stream(),串行流
    • Collection.parallelStream(),并行流
    • Arrays.stream(T[] array),built-in 数组
  • Stream 静态工厂方法
    • Stream.builder(),手动构建
    • Stream.empty(),空 Stream 流
    • Stream.of(T t),构造单个元素的 Stream 流
    • Stream.of(T... values),构造多个元素的 Stream 流(数组)
    • Stream.iterate(final T seed, final UnaryOperator<T> f),根据给定种子迭代产生的无限 Stream 流
    • Stream.generate(Supplier<T> s),使用给定生成器产生的无限 Stream 流
  • StreamSupport 工具类
    • StreamSupport.stream(Spliterator<T> spliterator, boolean parallel),只需要提供 Spliterator 迭代器

Stream 流的两种操作类型

  • Intermediate(中间操作)一个流可以后面跟随零个或多个 intermediate 操作
    其主要目的是,对数据进行filter过滤、map映射,然后返回一个新的流,交给下一个操作使用。
    中间操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,可以理解为仅仅注册一个操作。
  • Terminal(末端操作)一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作
    Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个side effect(副作用)

我们可以这样简单的理解,Stream 里有个操作函数的集合:
每次 intermediate 操作就是把 intermediate 函数放入这个集合中;
在 terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

short-circuiting 短路操作
还有一种操作被称为short-circuiting(短路),用以指:
1) 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
2) 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件

相信大家看完上面这段话还是不太理解什么是短路操作,其实说的直白一点,对于一个无限大的 Stream,如果没有一个 short-circuiting 操作,那么它将一直死循环下去,因为流是无限的。把 Stream 比作一个for(;;)循环的话,就更好理解了,如果没有在适当的时侯出现break语句,那么将无限循环下去。

基本类型的 Stream 流
对于基本数值型,目前有三种对应的包装类型 Stream:IntStreamLongStreamDoubleStream
虽然我们也可以用Stream<Integer>Stream<Long>Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

Stream 使用详解
简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)

  • filter,过滤,筛选出符合预期的元素,然后返回由它们组成的新 Stream,这是一个 intermediate 操作;
  • map,映射,对每个元素进行相应的加工,然后返回由它们组成的新 Stream,这是一个 intermediate 操作;
  • reduce,归约,使用给定初始值,递归的累加每个元素,返回累加的结果,这是一个 terminal 操作。

所有的 intermediate 操作都是返回一个新 Stream 对象,而原来的 Stream 对象则被 close。

Stream 常用方法
Intermediate
map、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered 等
Terminal
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator 等
Short-circuiting
anyMatch、allMatch、noneMatch、findFirst、findAny、limit 等

其实很好分辨,如果方法的返回值类型为 Stream,一般来说都是 intermediate 操作;反之,则一般为 terminal 操作。

java.util.stream包概览

  • Interface接口
    • BaseStream
    • Stream
    • Stream.Builder
    • IntStream
    • IntStream.Builder
    • LongStream
    • LongStream.Builder
    • DoubleStream
    • DoubleStream.Builder
    • Collector,收集器,高级版的 reduce
  • Class
    • Collectors,Collector 的工具类
    • StreamSupport,Stream 的工具类
  • Enum枚举
    • Collector.Characteristics,收集器的相关特性

BaseStream

Stream

IntStream

LongStream

DoubleStream

StreamSupport

java.util.stream.StreamSupport工具类,用于构建 Stream 流,仅需要提供 spliterator 迭代器即可。

Collector

java.util.stream.Collector收集器,和 reduce 很相似,可以理解为高级点的 reduce。

Collectors

java.util.stream.Collectors工具类,提供了许多常用的 Collector 收集器。

例子,将学生按成绩分为不同等级:

Spliterator

Iterator:迭代器,主要方法hasNext()next(),只能逐个进行遍历,不适用于在多个线程之间使用,需要手动同步,并且效率不高。
Spliterator:分割迭代器,tryAdvance()逐个遍历、forEachRemaining()一次性遍历所有剩余的元素,使用函数式编程风格,最大的特点是可以将元素分割成多份,分别交于不同的线程去遍历,提高效率。

注意,Stream 和 Spliterator 的区别:
Stream:Stream 的出现不是要替代现有迭代器,Stream 注重于弥补 Java 函数式编程的不足,如 filter()、map()、reduce(),很多函数式编程语言基本都将它们内置到语言的标准库中了。
Spliterator:Java8 引入的新迭代器,和 Iterator 不同的是,它可以转换为 Stream 流,使用聚合操作,函数式编程风格,同时内置支持并行迭代。

说得简单一些,Stream 的出现是建立在 Spliterator 的基础上的,只要有 Spliterator 迭代器,就可以构造一个 Stream 流。

我们知道,所有的 Java 集合对象(List、Set、Queue)都是继承自 Collection 接口,而 Collection 接口的父接口是java.lang.Iterable
为了无缝的衔接 Java8 中的 Spliterator 迭代器,Java8 对 Iterable 接口进行了修改,添加了spliterator()方法,用于获取 Spliterator 迭代器。

而有了 Spliterator 迭代器,我们就可以利用 StreamSupport 构造 Stream 流,非常方便;而对于 built-in 数组,我们则可以利用 Arrays 工具类,构造 Stream 流。

java.util.Spliterator接口

Spliterator.OfPrimitive

OfPrimitive 是 Spliterator 的子接口,是一个专门用于 Primitive 基本类型的 Spliterator 迭代器;
而 OfPrimitive 有三个主要子接口:Spliterator.OfInt、Spliterator.OfLong、Spliterator.OfDouble。

之所以专门为 int、long、double 提供专门的 Spliterator,是因为 boxing、unboxing 很费时,有不必要的开销。

Spliterator.OfInt

Spliterator.OfLong

Spliterator.OfDouble

Spliterators

java.util.Spliterators工具类,提供了实用的静态方法用以创建 Spliterator 迭代器

Spliterators.AbstractSpliterator

Spliterators 工具类除了提供静态方法用于创建 Spliterator 迭代器,还提供了几个静态内部类(抽象类):

  • Spliterators.AbstractSpliterator
  • Spliterators.AbstractIntSpliterator
  • Spliterators.AbstractLongSpliterator
  • Spliterators.AbstractDoubleSpliterator

如果我们需要实现一个自定义的 Spliterator,那么就可以继承自这几个抽象类。

Spliterators.AbstractIntSpliterator

Spliterators.AbstractLongSpliterator

Spliterators.AbstractDoubleSpliterator

PrimitiveIterator

java.util.PrimitiveIterator接口,Iterator 的子接口,是基本类型的专属迭代器,减少不必要的 boxing、unboxing 开销,Java8 引入。
PrimitiveIterator 主要有 3 个子接口,PrimitiveIterator.OfInt、PrimitiveIterator.OfLong、PrimitiveIterator.OfDouble。

PrimitiveIterator.OfInt

PrimitiveIterator.OfLong

PrimitiveIterator.OfDouble

DateTime API

java.time包,用于取代之前的java.util.Datejava.util.Calendar类。
因为 Java8 之前的 DateTime API 实在太难用了,对象居然是可变的,而且是非线程安全的。

新 API 的特点
所有的时间对象都是不可变的,并且它们都是线程安全的。

主要的类

  • Clock,时钟,提供对某个特定时区的瞬时时间、日期和时间的访问
  • Instant,瞬时时间,自1970-01-01 00:00:00 UTC起的秒数
  • ZoneId,时区信息,如"Asia/Shanghai"
  • LocalDate,本地日期,如2016-10-10,不包含时区信息
  • LocalTime,本地时间,如09:28:55.388,不包含时区信息
  • LocalDateTime,本地日期时间,不包含时区信息
  • ZonedDateTime,带时区信息的日期时间
  • DateTimeFormatter,提供 String 和 时间对象 的互转
  • Duration,表示两个 time 之间的时间段,使用秒、纳秒衡量
  • Period,表示两个 date 之间的时间段,使用年、月、日衡量
  • Yearx年、YearMonthx年x月、MonthDayx月x日
  • ChronoField时间字段(枚举)、ChronoUnit时间单位(枚举)
  • Month月份(枚举)、DayOfWeek星期(枚举)

java.time.temporal.TemporalAccessor,所有时间对象的父接口,定义对时间对象的只读访问

  • boolean isSupported(TemporalField field),是否支持指定时间字段;
  • default ValueRange range(TemporalField field),获取给定时间字段的有效取值范围;
  • default int get(TemporalField field),获取给定时间字段的当前取值,返回值类型为 int;
  • long getLong(TemporalField field),获取给定时间字段的当前取值,返回值类型为 long;
  • default <R> R query(TemporalQuery<R> query),使用给定查询器查询当前对象。

java.time.temporal.Temporal,定义对时间对象的读写访问,是 TemporalAccessor 的子接口。

  • boolean isSupported(TemporalUnit unit),是否支持给定的时间单位;
  • default Temporal with(TemporalAdjuster adjuster),调整当前对象;
  • Temporal with(TemporalField field, long newValue),调整给定时间字段;
  • default Temporal plus(TemporalAmount amount),加上给定时间量;
  • Temporal plus(long amountToAdd, TemporalUnit unit),加上给定时间量;
  • default Temporal minus(TemporalAmount amount),减去给定时间量;
  • default Temporal minus(long amountToSubtract, TemporalUnit unit),减去给定时间量;
  • long until(Temporal endExclusive, TemporalUnit unit),计算两个时间的时间差,以给定单位衡量;

java.time.temporal.TemporalAmount,表示一个时间量,子类:Duration、Period

  • long get(TemporalUnit unit),以给定时间单位计算时间量;
  • List<TemporalUnit> getUnits(),获取当前时间量的底层支持单位;
  • Temporal addTo(Temporal temporal),加上当前时间量;
  • Temporal subtractFrom(Temporal temporal),减去当前时间量;

java.time.temporal.TemporalField,定义时间字段的接口,子类:ChronoField(标准时间字段)

  • default String getDisplayName(Locale locale),获取特定语言环境下的字符串描述;
  • TemporalUnit getBaseUnit(),获取当前字段的基本单位;
  • TemporalUnit getRangeUnit(),获取当前字段的范围单位;
  • ValueRange range(),获取当前字段的有效取值范围;
  • boolean isDateBased(),检查当前字段是否为 date 组件;
  • boolean isTimeBased(),检查当前字段是否为 time 组件;
  • boolean isSupportedBy(TemporalAccessor temporal),测试给定时间对象是否支持当前字段;
  • ValueRange rangeRefinedBy(TemporalAccessor temporal),从指定时间对象获取此字段的有效取值范围以优化结果;
  • long getFrom(TemporalAccessor temporal),获取指定时间对象的此字段值;
  • <R extends Temporal> R adjustInto(R temporal, long newValue),调整给定时间对象的当前字段值;
  • String toString(),获取当前字段的字符串描述;

java.time.temporal.TemporalUnit,定义时间单位的接口,子类:ChronoUnit(标准时间单位)

  • Duration getDuration(),获取当前单位的持续时间,可能为估计值;
  • boolean isDurationEstimated(),判断当前单位的持续时间是否为估计值;
  • boolean isDateBased(),判断当前单位是否为 date 组件;
  • boolean isTimeBased(),判断当前单位是否为 time 组件;
  • default boolean isSupportedBy(Temporal temporal),检查给定 Temporal 是否支持当前单位;
  • <R extends Temporal> R addTo(R temporal, long amount),添加给定单位长度到指定时间对象;
  • long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive),计算两个时间的时间差,以当前单位计算;
  • String toString(),获取当前单位的字符串描述;

java.time.temporal.TemporalAdjuster,函数式接口,调整器,用于调整 Temporal 对象;

  • Temporal adjustInto(Temporal temporal),调整传入的 Temporal 对象;

java.time.temporal.TemporalQuery,函数式接口,定义时间对象的查询策略

  • R queryFrom(TemporalAccessor temporal),从给定 TemporalAccessor 对象查询想要的结果 R;

ChronoField

java.time.temporal.ChronoField,定义标准时间字段的枚举类。

ChronoUnit

java.time.temporal.ChronoUnit,定义标准时间单位的枚举类。

Month

java.time.Month,定义 12 个月份的枚举类。

DayOfWeek

java.time.DayOfWeek,定义星期几的枚举类。

Duration

java.time.Duration,时间量,以秒、纳秒衡量

Period

java.time.Period,时间量,以年、月、日衡量

Year

java.time.Year,年份

YearMonth

java.time.YearMonth,某年某月

MonthDay

java.time.MonthDay,某月某日

ZoneId

java.time.ZoneId,时区ID,如"Asia/Shanghai"上海时区(UTC+8)

Clock

java.time.Clock,时钟

Instant

java.time.Instant瞬时时间,java.util.Date对象表示的就是一个瞬时时间

LocalDate

java.time.LocalDate,本地日期,不带时区信息

LocalTime

java.time.LocalTime,本地时间,不带时区信息

LocalDateTime

java.time.LocalDateTime,LocalDate、LocalTime 的结合体,不带时区信息

ZonedDateTime

java.time.ZonedDateTime,带时区信息的 DateTime

DateTimeFormatter

java.time.format.DateTimeFormatter,时间格式化工具