Java 8 相关整理
2018-05-23 本文已影响160人
58bc06151329
文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
1. Lambda 表达式
- Lambda 允许把 函数 作为一个方法的参数传递进方法中。
(parameters) -> expression
(parameters) -> { statements; }
1.1 表达式重要特征
- 可选类型声明
- 不需要声明参数类型,编译器可以统一识别参数值。
public class Test {
public static void main(String[] args) {
Operation subtractionA = (int a, int b) -> a - b;
Operation subtractionB = (a, b) -> a - b;
}
interface Operation {
int operation(int a, int b);
}
}
- 可选的参数圆括号
- 一个参数无需定义圆括号,但多个参数需要定义圆括号。
public class Test {
public static void main(String[] args) {
OperationA subtractionA = (int a, int b) -> a - b;
OperationB subtractionB = a -> a * 2;
}
interface OperationA {
int operation(int a, int b);
}
interface OperationB {
int operation(int a);
}
}
- 可选的大括号
- 如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字
- 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
public class Test {
public static void main(String[] args) {
OperationA subtractionA = (int a, int b) -> {
return a - b;
};
}
interface OperationA {
int operation(int a, int b);
}
}
- 使用 Lambda 表达式需要注意以下两点
- Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,使用各种类型的 Lambda 表达式来定义 OperationA/B 接口的方法。
- Lambda 表达式免去了使用匿名方法的麻烦,给予 Java 简单但是强大的函数化的编程能力。
1.2 变量作用域
- Lambda 表达式只能引用标记了 final 的外层局部变量,不能在 Lambda 内部修改定义在域外的局部变量,否则会编译错误。
- 也可以直接在 Lambda 表达式中访问外层的局部变量(隐性的具有 final 的语义)。
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
public class Test {
private static int c = 1;
public static void main(String[] args) {
Operation subtractionA = (a, b) -> a + b + c;
}
interface Operation {
int operation(int a, int b);
}
}
2. 方法引用
-
可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 Lambda 联合使用,可以使语言的构造更紧凑简洁,减少冗余代码。
- 通过方法的名字来指向一个方法。
- 使用一对冒号
::
。
-
构造器引用
- 语法为 Class::new,或者 Class< T >::new。
public class Test {
public static void main(String[] args) {
Operation operation = Operation.create(Operation::new);
}
static class Operation {
public static Operation create(final Supplier<Operation> supplier) {
return supplier.get();
}
}
}
- 静态方法引用
- 语法为 Class::staticMethod。
- 特定类的任意对象的方法引用
- 语法为 Class::method。
public class Test {
public static void main(String[] args) {
List<Operation> operations = Arrays.asList(new Operation());
operations.forEach(Operation::print);
}
static class Operation {
public static void print(final Operation operation) {
System.out.print(operation.toString());
}
}
}
public class Test {
public static void main(String[] args) {
final Operation operation = new Operation();
List<Operation> operations = Arrays.asList(operation);
operations.forEach(System.out::print);
}
static class Operation {
}
}
- 特定对象的方法引用
- 语法为 instance::method。
public class Test {
public static void main(String[] args) {
final Operation operation = new Operation();
List<Operation> operations = Arrays.asList(operation);
operations.forEach(operation::print);
}
static class Operation {
public void print(final Operation Operation) {
System.out.print(Operation.toString());
}
}
}
3. 函数式接口
-
函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
-
函数式接口可以被隐式转换为 Lambda 表达式。
-
JDK 1.8 之前已有函数式接口
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
-
JDK 1.8 新增加函数接口
- java.util.function
- 包含了很多类,用来支持 Java 的 函数式编程。
- java.util.function
序号 | 接口 | 描述 |
---|---|---|
1 | BiConsumer<T,U> | 代表了一个接受两个输入参数的操作,并且不返回任何结果。 |
2 | BiFunction<T,U,R> | 代表了一个接受两个输入参数的方法,并且返回一个结果。 |
3 | BinaryOperator<T> | 代表了一个作用于两个同类型操作符的操作,并且返回了操作符同类型的结果。 |
4 | BiPredicate<T,U> | 代表了一个两个参数的 boolean 值方法。 |
5 | BooleanSupplier | 代表了 boolean 值结果的提供方。 |
6 | Consumer<T> | 代表了接受一个输入参数并且无返回的操作。 |
7 | DoubleBinaryOperator | 代表了作用于两个 double 值操作符的操作,并且返回一个 double 值的结果。 |
8 | DoubleConsumer | 代表一个接受 double 值参数的操作,并且不返回结果。 |
9 | DoubleFunction<R> | 代表接受一个 double 值参数的方法,并且返回结果。 |
10 | DoublePredicate<R> | 代表一个拥有 double 值参数的 boolean 值方法。 |
11 | DoubleSupplier | 代表一个 double 值结构的提供方。 |
12 | DoubleToIntFunction | 接受一个 double 类型输入,返回一个 int 类型结果。 |
13 | DoubleToLongFunction | 接受一个 double 类型输入,返回一个 long 类型结果。 |
14 | DoubleUnaryOperator | 接受一个参数同为类型 double,返回值类型也为 double 。 |
15 | Function<T,R> | 接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator | 接受两个参数同为类型 int,返回值类型也为 int 。 |
17 | IntConsumer | 接受一个 int 类型的输入参数,无返回值 。 |
18 | IntFunction<R> | 接受一个 int 类型输入参数,返回一个结果 。 |
19 | IntPredicate | 接受一个 int 输入参数,返回一个 boolean 的结果。 |
20 | IntSupplier | 无参数,返回一个 int 类型结果。 |
21 | IntToDoubleFunction | 接受一个 int 类型输入,返回一个 double 类型结果 。 |
22 | IntToLongFunction | 接受一个 int 类型输入,返回一个 long 类型结果。 |
23 | IntUnaryOperator | 接受一个参数同为类型 int,返回值类型也为 int 。 |
24 | LongBinaryOperator | 接受两个参数同为类型 long,返回值类型也为 long。 |
25 | LongConsumer | 接受一个 long 类型的输入参数,无返回值。 |
26 | LongFunction<R> | 接受一个 long 类型输入参数,返回一个结果。 |
27 | LongPredicate | 接受一个 long 输入参数,返回一个 boolean 类型结果。 |
28 | LongSupplier | 无参数,返回一个结果 long 类型的值。 |
29 | LongToDoubleFunction | 接受一个 long 类型输入,返回一个 double 类型结果。 |
30 | LongToIntFunction | 接受一个 long 类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator | 接受一个参数同为类型 long,返回值类型也为 long。 |
32 | ObjDoubleConsumer<T> | 接受一个 object 类型和一个 double 类型的输入参数,无返回值。 |
33 | ObjIntConsumer<T> | 接受一个 object 类型和一个 int 类型的输入参数,无返回值。 |
34 | ObjLongConsumer<T> | 接受一个 object 类型和一个 long 类型的输入参数,无返回值。 |
35 | Predicate<T> | 接受一个输入参数,返回一个 boolean 结果。 |
36 | Supplier<T> | 无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U> | 接受两个输入参数,返回一个 double 类型结果。 |
38 | ToDoubleFunction<T> | 接受一个输入参数,返回一个 double 类型结果。 |
39 | ToIntBiFunction<T,U> | 接受两个输入参数,返回一个 int 类型结果。 |
40 | ToIntFunction<T> | 接受一个输入参数,返回一个 int 类型结果。 |
41 | ToLongBiFunction<T,U> | 接受两个输入参数,返回一个 long 类型结果。 |
42 | ToLongFunction<T> | 接受一个输入参数,返回一个 long 类型结果。 |
43 | UnaryOperator<T> | 接受一个参数为类型 T,返回值类型也为 T。 |
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
eval(list, n -> true);
}
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for (Integer n : list) {
if (predicate.test(n)) {
System.out.println(n);
}
}
}
}
4. 默认方法
- 默认方法是接口可以有实现方法,而且不需要实现类去实现其方法。
- 只需在方法名前面加个 default 关键字即可实现默认方法。
public interface IOperation {
default void print(){
System.out.println("");
}
}
4.1 多个默认方法
- 一个类实现了多个接口,且这些接口有相同的默认方法。
public interface IOperationA {
default void print(){
System.out.println("");
}
}
public interface IOperationB {
default void print(){
System.out.println("");
}
}
- 第一个解决方案
- 创建自己的默认方法,覆盖重写接口的默认方法。
public class Operation implements IOperationA, IOperationB {
default void print(){
System.out.println("");
}
}
- 第二种解决方案
- 使用 super 来调用指定接口的默认方法。
public class Operation implements IOperationA, IOperationB {
public void print(){
IOperationA.super.print();
}
}
4.2 静态默认方法
- 接口可以声明(并且可以提供实现)静态方法。
public interface Operation {
default void printA(){
System.out.println("A");
}
// 静态方法
static void printB(){
System.out.println("B");
}
}
5. Stream
- Java 8 API 添加了一个新的抽象称为流 Stream,可以以一种声明的方式处理数据。
- Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
- 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
- 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
List<Integer> transactionsIds = widgets.stream().filter(b -> b.getColor() == RED).sorted((x,y) -> x.getWeight() - y.getWeight()).mapToInt(Widget::getWeight).sum();
-
像 filter 这样只描述 Stream,最终不产生新集合的方法叫作惰性求值方法。而像 sum 这样最终会从 Stream 产生值的方法叫作及早求值方法。
- 如果返回值是Stream,那么就是惰性求值。
- 如果返回值不是 Stream 或者是 void,那么就是及早求值。
-
在一个 Stream 操作中,可以有多次惰性求值,但有且仅有一次及早求值。
-
Stream(流)是一个来自数据源的元素队列并支持聚合操作。
- 元素是特定类型的对象,形成一个队列。 Java 中的 Stream 并不会存储元素,而是按需计算。
- 数据源流的来源。 可以是集合,数组,I/O channel, 产生器 generator 等。
- 聚合操作类似 SQL 语句一样的操作, 如 filter,map,reduce,find,match,sorted 等。
-
和以前的 Collection 操作不同, Stream 操作还有两个基础的特征
- Pipelining
- 中间操作都会返回流对象本身。
- 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
- 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
- 内部迭代
- 以前对集合遍历都是通过 Iterator 或者 For-Each 的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。
- Stream 提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
- Pipelining
5.1 生成流
5.1.1 静态工厂方法。
5.1.1.1 of
- 其生成的 Stream 是有限长度的,Stream 的长度为其内的元素个数。
方法 | 说明 |
---|---|
- of(T... values) | 返回含有多个 T 元素的 Stream |
- of(T t) | 返回含有一个 T 元素的 Stream |
Stream<Integer> integerStream = Stream.of(1, 2, 3);
Stream<String> stringStream = Stream.of("A");
5.1.1.2 generator
- generator 方法,返回一个 无限 长度的 Stream,其元素由 Supplier 接口的提供。
- 在 Supplier 里是一个函数接口,只封装了一个 get() 方法,其用来返回任何泛型的值,该结果在不同的时间内,返回的可能相同也可能不相同,没有特殊的要求。
- 通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。
- 把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。
方法 | 说明 |
---|---|
- generate(Supplier<T> s) | 返回一个无限长度的 Stream |
Stream.generate(java.lang.Math::random);
一般无限长度的 Stream 会与 filter、limit 等配合使用,否则 Stream 会无限制的执行下去。
5.1.1.3 iterate
- iterate方法,其返回的也是一个无限长度的 Stream,与 generate 方法不同的是,其是通过函数 f 迭代对给指定的元素种子而产生无限连续有序 Stream。
- 其中包含的元素可以认为是 seed,f(seed),f(f(seed)) 无限循环。
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
- 上面示例,种子为 1,也可认为该 Stream 的第一个元素,通过 f 函数来产生第二个元素。接着,第二个元素,作为产生第三个元素的种子,从而产生了第三个元素,以此类推下去。
5.1.1.4 empty
- empty方法返回一个空的顺序 Stream,该 Stream 里面不包含元素项。
Stream.empty();
5.1.2 Collection 接口和数组的默认方法
- 在 Collection 接口中,定义了一个默认方法 stream(),用来生成一个 Stream。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).collect(Collectors.toList());
- 在 Collection 接口中,定义了一个方法 parallelStream(),用来生成一个并行流。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
// 获取对应的平方数
List<Integer> squaresList = numbers.parallelStream().map( i -> i*i).collect(Collectors.toList());
- 在 Arrays 类,封装了一些列的 Stream 方法,不仅针对于任何类型的元素采用了泛型,更对于基本类型作了相应的封装,以便提升 Stream 的处理效率。
int id[] = new int[]{1, 2, 3, 4};
Arrays.stream(id).forEach(System.out::println);
5.1.3 其他
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
5.2 中间操作(Intermediate)
- 主要是用来对 Stream 做出相应转换及限制流,实际上是将源 Stream 转换为一个新的Stream,以达到需求效果。
5.2.1 concat
- concat 方法将两个 Stream 连接在一起,合成一个 Stream。
- 若两个输入的 Stream 都是排序的,则新 Stream 也是排序的。
- 若输入的 Stream 中任何一个是并行的,则新的 Stream 也是并行的。
- 若关闭新的 Stream 时,原两个输入的 Stream 都将执行关闭处理。
Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5)).forEach(System.out::println);
5.2.2 distinct
- distinct 方法以达到去除掉原 Stream 中重复的元素,生成的新 Stream 中没有没有重复的元素。
Stream.of(1, 2, 3, 1, 2, 3).distinct().forEach(System.out::println);
5.2.3 filter
- filter 方法用于通过设置的条件过滤出元素。
// 获取空字符串的数量
Stream.of("1", "2", "3", "4", "", "", "7", "", "").filter(string -> string.isEmpty()).count();
5.2.4 map
- map 方法用于映射每个元素到对应的结果。
// 获取对应的平方数
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).map(i -> i * i).collect(Collectors.toList());
5.2.5 flatMap
- flatMap 方法与 map 方法类似,都是将原 Stream 中的每一个元素通过转换函数转换,不同的是,该换转函数的对象是一个 Stream,也不会再创建一个新的 Stream,而是将原 Stream的元素取代为转换的 Stream。
- 如果转换函数生产的 Stream 为 null,应由空 Stream 取代。
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).flatMap(i -> i * i).collect(Collectors.toList());
5.2.6 peek
- peek 方法生成一个包含原 Stream 的所有元素的新 Stream,同时会提供一个消费函数(Consumer实例),新 Stream 每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行。
Stream.of(1, 2, 3).peek(x -> System.out.print(x));
5.2.7 skip
- skip 方法将过滤掉原 Stream 中的前 N 个元素,返回剩下的元素所组成的新 Stream。
- 如果原 Stream 的元素个数大于 N,将返回原 Stream 的后(原 Stream 长度 - N)个元素所组成的新 Stream。
- 如果原 Stream 的元素个数小于或等于 N,将返回一个空 Stream。
Stream.of(1, 2, 3,4,5) .skip(2) .forEach(System.out::println);
5.2.8 sorted
- sorted 方法将对原 Stream 进行排序,返回一个有序列的新 Stream。
- sorterd有两种变体 sorted(),sorted(Comparator),前者将默认使用 Object.equals(Object) 进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。
Stream.of(5, 4, 3, 2, 1).sorted().forEach(System.out::println);
5.3 最终操作(Terminal)
- 在一次聚合操作中,可以有多个中间操作,但是有且只有一个最终操作。
5.3.1 collect
- 通过 collect 收集器,应用 Collector 工具。
5.3.1.1 转换成其他集合
- toList
List<Integer> collectList = Stream.of(1, 2, 3, 4).collect(Collectors.toList());
- toSet
Set<Integer> collectSet = Stream.of(1, 2, 3, 4).collect(Collectors.toSet());
- toCollection
- 其接受的函数参数必须继承于 Collection。
TreeSet<Integer> collectSet = Stream.of(1, 2, 3, 4).collect(Collectors.toCollection(TreeSet::new));
- toMap
- 若 Stream 中重复值,导致 Map 中 key 重复,在运行时会报异常
java.lang.IllegalStateException: Duplicate key
- 若 Stream 中重复值,导致 Map 中 key 重复,在运行时会报异常
public Map<Long, String> getIdNameMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}
5.3.1.2 转成值
方法 | 说明 |
---|---|
averagingDouble | 求平均值,Stream 的元素类型为 double。 |
averagingInt | 求平均值,Stream 的元素类型为 int。 |
averagingLong | 求平均值,Stream 的元素类型为 long。 |
counting | Stream 的元素个数 |
maxBy | 在指定条件下的,Stream 的最大元素。 |
minBy | 在指定条件下的,Stream 的最小元素。 |
reducing | reduce 操作。 |
summarizingDouble | 统计 Stream 的数据(double)状态,其中包括 count,min,max,sum 和平均。 |
summarizingInt | 统计 Stream 的数据(int)状态,其中包括 count,min,max,sum 和平均。 |
summarizingLong | 统计 Stream 的数据(long)状态,其中包括 count,min,max,sum 和平均。 |
summingDouble | 求和,Stream 的元素类型为 double。 |
summingInt | 求和,Stream 的元素类型为 int。 |
summingLong | 求和,Stream 的元素类型为 long。 |
Optional<Integer> collectMaxBy = Stream.of(1, 2, 3, 4).collect(Collectors.maxBy(Comparator.comparingInt(o -> o)));
5.3.1.3 分割数据块
- collect 的一个常用操作将 Stream 分解成两个集合。
- 使用 partitioningBy,可以将 Stream 分解。
- partitioningBy 方法,接受一个流,并将其分成两部分:使用 Predicate 对象,指定条件并判断一个元素应该属于哪个部分,并根据布尔值返回一个 Map 到列表。
- 对于 key 为 true 所对应的 List 中的元素,满足 Predicate 对象中指定的条件。
- key 为 false 所对应的 List 中的元素,不满足 Predicate 对象中指定的条件。
- partitioningBy 方法,接受一个流,并将其分成两部分:使用 Predicate 对象,指定条件并判断一个元素应该属于哪个部分,并根据布尔值返回一个 Map 到列表。
Map<Boolean, List<Integer>> collectParti = Stream.of(1, 2, 3, 4).collect(Collectors.partitioningBy(it -> it % 2 == 0));
5.3.1.4 数据分组
- 数据分组是一种更自然的分割数据操作, 与将数据分成 true 和 false 两部分不同,可以使用任意值对数据分组。
- groupingBy 接受一个分类函数,用来对数据分组,就像 partitioningBy 一样,接受一个
Predicate 对象将数据分成 true 和 false 两部分。
Map<Boolean, List<Integer>> collectGroup= Stream.of(1, 2, 3, 4).collect(Collectors.groupingBy(it -> it > 3));
5.3.1.5 字符串
- joining 函数接受三个参数,分别表示允(用以分隔元素)、前缀和后缀。
Stream.of("1", "2", "3", "4").collect(Collectors.joining(",", "[", "]"));
5.3.1.6 组合 Collector
- 在 partitioningBy 方法中,不仅传递了条件函数,同时还可以传入第二个收集器,用以收集最终结果的一个子集,这些收集器叫作 下游收集器。
- 收集器是生成最终结果的一剂配方,下游收集器则是生成部分结果的配方,主收集器中会用到下游收集器。
- 这种组合使用收集器的方式,使得它们在 Stream 类库中的作用更加强大。
Map<Boolean, Long> partiCount = Stream.of(1, 2, 3, 4, 5).collect(Collectors.partitioningBy(it -> it.intValue() % 2 == 0, Collectors.counting()));
System.out.println("partiCount: " + partiCount);
// 打印结果
// partiCount: {false=3, true=2}
5.3.2 count
- count 方法将返回 Stream 中元素的个数。
Stream.of(1, 2, 3, 4, 5).count();
5.3.3 forEach
- 提供了新的方法 forEach 来迭代流中的每个数据。
Stream.of(1, 2, 3).forEach(System.out::println);
5.3.4 forEachOrdered
- forEachOrdered 方法与 forEach 类似,都是遍历 Stream 中的所有元素,不同的是,如果该
Stream 预先设定了顺序,会按照预先设定的顺序执行(Stream 是无序的),默认为元素插入的顺序。
Stream.of(1, 2, 3).forEachOrdered(System.out::println);
5.3.5 max
- max 方法根据指定的 Comparator,返回一个 Optional,该 Optional 中的 value 值就是
Stream 中最大的元素。 - 原 Stream 根据比较器 Comparator,进行排序(升序或者是降序),所谓的最大值就是从新进行排序的,max 就是取重新排序后的最后一个值,而 min 取排序后的第一个值。
Optional<Integer> max = Stream.of(1, 2, 3, 4, 5).max((o1, o2) -> o2 - o1);
5.3.6 min
Optional<Integer> max = Stream.of(1, 2, 3, 4, 5).max((o1, o2) -> o1 - o2);
5.4 非短路操作(Short-circuiting)
5.4.1 allMatch
- allMatch 操作用于判断 Stream 中的元素是否全部满足指定条件。如果全部满足条件返回
true,否则返回 false。
Stream.of(1, 2, 3, 4).allMatch(integer -> integer > 2);
5.4.2 anyMatch
- anyMatch 操作用于判断 Stream 中的是否有满足指定条件的元素。如果最少有一个满足条件返回 true,否则返回 false。
Stream.of(1, 2, 3, 4).anyMatch(integer -> integer > 3);
5.4.3 findAny
- findAny 操作用于获取含有 Stream 中的某个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。
- 由于此操作的行动是不确定的,其会自由的选择 Stream 中的任何元素。
- 在并行操作中,在同一个 Stram 中多次调用,可能会不同的结果。
Stream.of(1, 2, 3, 4).findAny();
5.4.4 findFirst
- findFirst 操作用于获取含有 Stream 中的第一个元素的 Optional,如果 Stream 为空,则返回一个空的 Optional。
- 若 Stream 并未排序,可能返回含有 Stream 中任意元素的 Optional。
Stream.of(1, 2, 3, 4).findFirst();
5.4.5 limit
- limit 方法将截取原 Stream,截取后 Stream 的最大长度不能超过指定值 N。
- 如果原 Stream 的元素个数大于 N,将截取原 Stream 的前 N 个元素。
- 如果原 Stream 的元素个数小于或等于 N,将截取原 Stream 中的所有元素。
Stream.of(1, 2, 3,4,5).limit(2).forEach(System.out::println);
5.4.6 noneMatch
- noneMatch 方法将判断 Stream 中的所有元素是否满足指定的条件,如果所有元素都不满足条件,返回 true,否则,返回 false。
Stream.of(1, 2, 3, 4, 5).noneMatch(integer -> integer > 10);
5.4.7 reduce
- reduce 操作可以实现从 Stream 中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。
Stream.of(1, 2, 3, 4)
.reduce((acc, item) -> {
acc += item;
return acc;
});
6. Optional
- Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用
get() 方法会返回该对象。 - Optional 是个容器,它可以保存类型 T 的值,或者仅仅保存 null。
- Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常。
序号 | 方法 | 描述 |
---|---|---|
1 | static <T> Optional<T> empty() | 返回空的 Optional 实例。 |
2 | boolean equals(Object obj) | 判断其他对象是否等于 Optional。 |
3 | Optional<T> filter(Predicate<? super <T> predicate) | 如果值存在,并且这个值匹配给定的 predicate,返回一个 Optional 用以描述这个值,否则返回一个空的 Optional。 |
4 | <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) | 如果值存在,返回基于 Optional 包含的映射方法的值,否则返回一个空的 Optional。 |
5 | T get() | 如果在这个 Optional 中包含这个值,返回值,否则抛出异常:NoSuchElementException |
6 | int hashCode() | 返回存在值的哈希码,如果值不存在 返回 0。 |
7 | void ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
8 | boolean isPresent() | 如果值存在则方法会返回 true,否则返回 false。 |
9 | <U>Optional<U> map(Function<? super T,? extends U> mapper) | 如果存在该值,提供的映射方法,如果返回非 null,返回一个 Optional 描述结果。 |
10 | static <T> Optional<T> of(T value) | 返回一个指定非 null 值的 Optional。 |
11 | static <T> Optional<T> ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。 |
12 | T orElse(T other) | 如果存在该值,返回值, 否则返回 other。 |
13 | T orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 |
14 | <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
15 | String toString() | 返回一个 Optional 的非空字符串,用来调试。 |
Optional<Integer> a = Optional.ofNullable(value);
7. 日期时间
- 通过发布新的 Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
- 旧版的 Java 中,日期时间 API 存在诸多问题
- 非线程安全
- java.util.Date 是非线程安全的,所有的日期类都是可变的,这是 Java 日期类最大的问题之一。
- 设计很差
- Java 的日期/时间类的定义并不一致,在 java.util 和 java.sql 的包中都有日期类,此外用于格式化和解析的类在 java.text 包中定义。java.util.Date 同时包含日期和时间,而java.sql.Date 仅包含日期,将其纳入 java.sql 包并不合理,另外这两个类都有相同的名字。
- 时区处理麻烦
- 日期类并不提供国际化,没有时区支持,因此 Java 引入了 java.util.Calendar 和
java.util.TimeZone 类,但同样存在上述所有的问题。
- 日期类并不提供国际化,没有时区支持,因此 Java 引入了 java.util.Calendar 和
- 非线程安全
- Java 8 在 java.time 包下提供了很多新的 API。
- Local(本地)
- 简化了日期时间的处理,没有时区的问题。
- Zoned(时区)
- 通过制定的时区处理日期时间。
- Local(本地)
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
- 使用时区的日期时间
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
8. Base64
- Java 8 内置了 Base64 编码的编码器和解码器。
- Base64 工具类提供了一套静态方法获取下面三种 Base64 编解码器。
- 基本:输出被映射到一组字符 A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持 A-Za-z0-9+/。
- URL:输出映射到一组字符 A-Za-z0-9+_,输出是 URL 和文件。
- MIME:输出隐射到 MIME 友好格式。输出每行不超过 76 字符,并且使用 \r 并跟随 \n 作为分割。编码输出最后没有行分割。
- 内嵌类
序号 | 内嵌类 | 描述 |
---|---|---|
1 | static class Base64.Decoder | 该类实现一个解码器用于,使用 Base64 编码来解码字节数据。 |
2 | static class Base64.Encoder | 该类实现一个编码器,使用 Base64 编码来编码字节数据。 |
- 方法
序号 | 方法名 | 描述 |
---|---|---|
1 | static Base64.Decoder getDecoder() | 返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。 |
2 | static Base64.Encoder getEncoder() | 返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。 |
3 | static Base64.Decoder getMimeDecoder() | 返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。 |
4 | static Base64.Encoder getMimeEncoder() | 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。 |
5 | static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) | 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。 |
6 | static Base64.Decoder getUrlDecoder() | 返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。 |
7 | static Base64.Encoder getUrlEncoder() | 返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。 |
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 字符串 (基本) :" + base64encodedString);
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("TutorialsPoint?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);
9. Nashorn JavaScript
- 从 JDK 1.8 开始,Nashorn 取代 Rhino(JDK 1.6, JDK1.7)成为 Java 的嵌入式 JavaScript
引擎。- Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展。
- 它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码。
- 与先前的 Rhino 实现相比,这带来了 2 到 10 倍的性能提升。
9.1 Java 中调用 JavaScript
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
String name = "Runoob";
Integer result = null;
try {
nashorn.eval("print('" + name + "')");
result = (Integer) nashorn.eval("10 + 2");
}catch(ScriptException e){
System.out.println("执行脚本错误: "+ e.getMessage());
}
System.out.println(result.toString());
9.2 JavaScript 中调用 Java
var BigDecimal = Java.type('java.math.BigDecimal');
function calculate(amount, percentage) {
var result = new BigDecimal(amount).multiply(
new BigDecimal(percentage)).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
return result.toPlainString();
}
var result = calculate(568000000000000000023,13.9);
print(result);