java8新特性

Java8 新特性总结

1
2
3
4
5
6
7
8
9
10
11
一、lamada、函数式编程
二、Stream介绍
Stream流⽔线解决⽅案
三、默认方法:
四、异步处理
五、时间处理类
六、why java8?
七、函数编程技巧
八、超越java 8—jdk版本重大特性总结
九、结论总结
十、附录

一、lamada、函数式编程

函数式编程:

即行为参数化;

lamada:

简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列列表、函数主体、返回类型,可能还有⼀一个可以︎出的异常列列表。

特点:︎︎︎︎匿名、函数、简洁、传递

可以在函数式接口上使⽤用Lambda表达式。

函数式接⼝:只定义⼀个抽象⽅方法的接⼝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
demo

Fruit apple = new Fruit("apple", 1);
Fruit banana = new Fruit("banana", 2);
List<Fruit> fruitList = Lists.newArrayList(apple, banana);

//java.util.function包中引⼊了⼏个新的函数式接⼝
// ⼀、⼀些函数式接⼝
// 1、Consumer
Consumer<? super Fruit> c = (Consumer<Fruit>) fruit ->
System.out.println(fruit.getName());
c.accept(apple);
fruitList.forEach(c);
//2、Supplier,好处:⽤的时候相当于每次直接从⼯⼚⾥⾯Lazy返回⼀个结果
Supplier<Fruit> ss = Fruit::new;
System.out.println(ss.get());
System.out.println(ss.get());
//3、Function
MyFunction<String, Integer, Fruit> sssa = Fruit::new;
sssa.apply("s", 32);
Function<Fruit, Integer> a = Fruit::getMoney;
// 4、Predicate
Predicate<Fruit> p = o -> o.getMoney() > 1;
Integer size
=fruitList.stream().filter(p).map(a).collect(Collectors.collectingAndThen(toLi
st(), List::size));


// ⾃定义Function⽀持多参数
@FunctionalInterface
public interface MyFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
}

二、Stream介绍:

    在调⽤collect之前,没有任何结果产⽣,实际上根本就没有从menu⾥选择元素。 流的处理过程

是一种内部迭代,** 你可以这么理解:链中的⽅方法调⽤用都在排队等待,直到调⽤用collect。Java 8的Stream以
其延迟︎︎性而著称,它们被︎刻意设计成这样,即︎︎︎︎︎︎延迟操作,有其独特的原因: Stream就像是⼀一个︎盒,它接收请
求⽣成结果。当你向一个 Stream发起一系列列的操作请求时,这 些请求只是被⼀一保存起来。只有当你
向Stream发起⼀个︎︎︎作时,才会实际地进⾏计算。这种设计具有显著的优点,特别是你需要对Stream进⾏多个操作时(你有可能先要进⾏filter操 作,紧接着做⼀个map,最后进⾏⼀次终端操作reduce;这种 ⽅式下Stream只需要遍历⼀次, 不需要为每个操作遍历⼀次所有的元素.

粗略地说,集合与流之间的差异就在于什么时进⾏计算。

图4-2显示了流操作的顺序:filter、map、limit、collect 每个操作简介如下。 举个例⼦:

1
2
List<String> dishenameList = menu.stream().filter(d -> d.getCalories() >
300).ma p(Dish::name).limit(3).collect(toList());

image-20211102185024921

流只能遍历⼀次。遍历完之后,我们就说这个流已经被消费了,——Streams库的内部迭代可以⾃动选择 ⼀种适 合你硬件的数据表示和并⾏实现。流利⽤了内部迭代:替你把迭代做了.

image-20211102190455717

image-20211102190514125

Stream流⽔水线解决⽅方案

Stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会
触发实际计算。中间操作⼜可以分为⽆状态的( Stateless )和有状态的( Stateful ),⽆无状态中间操作是指元素
的处理理不不受前面元素的影响,而有状态的中间操作必须等到所有元素处理理之后才知道最终结果,⽐如排
序是有状态操作,在读取所有元素之前并不能确定排序结果;结束操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果,⽐比如找到第一个满⾜足条件的元素。之所以要进行如此精细的划分,是因为底层对每⼀种情况的处理方式不同。

    很多Stream操作会需要⼀个回调函数(Lambda表达式),因此一个完整的操作是<*数据来源,操作、回调函数 > 构成的三元组。 Stream 中使⽤用 Stage 的概念来描述⼀个完整的操作,并⽤某种实例例化后的PipelineHelper*来代表Stage,将具有先后顺序的各个Stage连到⼀起,就构成了整个流⽔水线。跟

Stream相关类和接口的继承关系图示。

Stream流⽔水线组织结构示意图如下:

image-20211102191203210

图中通过 Collection.stream() ⽅法得到Head也就是stage0,紧接着调⽤⼀系列的中间操作,不断产 ⽣新的Stream。这些Stream对象以双向链表的形式组织在⼀起,构成整个流⽔线,由于每个Stage都 记录了前⼀个Stage和本次的操作以及回调函数,依靠这种结构就能建⽴起对数据源的所有操作。这就 是Stream记录操作的⽅式。当前Stage本身才知道该如何执⾏⾃⼰包含的动作。这就需要有某种协议来 协调相邻Stage之间的调⽤关系。这种协议由Sink接⼝完成.

image-20211102191357093

实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接⼝口⽅方法

1
2
3
遍历元素时调⽤用,接受⼀一个待处理理元素,并对元素进⾏行行处理理。Stage
把⾃自⼰己包含的操作和回调⽅方法封装到该⽅方法⾥里里,前⼀一个Stage只需要
调⽤用当前Stage.accept(T t)⽅方法就⾏行行了了。

图中通过Collection.stream()⽅方法得到 Head 也就是stage0,紧接着调⽤用⼀一系列列的中间操作,不不断产
⽣生新的Stream。 这些Stream对象以双向链表的形式组织在⼀一起,构成整个流⽔水线,由于每个Stage都
记录了了前⼀一个Stage和本次的操作以及回调函数,依靠这种结构就能建⽴立起对数据源的所有操作
。这就
是Stream记录操作的⽅方式。当前Stage本身才知道该如何执⾏行行⾃自⼰己包含的动作。这就需要有某种协议来
协调相邻Stage之间的调⽤用关系。这种协议由 Sink 接⼝口完成

实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接⼝口⽅方法

image-20211102191428454

Sink完美封装了了Stream每⼀步操作,并给出了[处理->转发]的模式来叠加操作。

举例:

image-20211102191456731

总⽽⾔之,流的使⽤⼀般包括三件事:

⼀个数据(如集合)来执⾏⼀个查询;

⼀个中间作,形成⼀条流的流⽔线;

⼀个操作,执⾏流⽔线,并能⽣成结果。

 以上,流的流⽔线背后的理念类似于构建器模式。在构建器模式中有⼀个调⽤链⽤来设⼀套配 (对流来 说这就是⼀个中间操作链),接着是调⽤built⽅法(对流来说就是终端操作)。 

如你所⻅,Stream API实现如此巧妙,即使我们使⽤外部迭代⼿动编写等价代码,也未必更加⾼效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
demo

//二、Stream
// 1、流的扁平化 flatMap
List<String> words = Lists.newArrayList("Hello", "World");
List<String> newWords = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());

// 2、归约 将流中所有元素反复结合/折叠起来,得到一个值
List<Integer> numbers = Lists.newArrayList(1, 2, 3);
int sum = numbers.stream().reduce(0, Integer::sum);

// 3、并行流(parallel),默认使⽤forkJoin(其实就是分治算法的并⾏版本)
//并⾏流并不总是⽐顺序流快

// ⾃动装和操作会⼤⼤低性能
fruitList.stream().parallel().collect(Collectors.toList());

// 方式1 自定义一个Collector收集器,定制自己的stream处理逻辑
List<Fruit> ppp = fruitList.stream().collect(new ToListCollector<>());
// 方式2,更紧促
List<Fruit> fruits = fruitList
.stream()
.collect(
ArrayList::new, //supplier 供应源
ArrayList::add, //accumulator 累加器
List::addAll //combiner 组合器
);

//6、 java.util.Optional 一个容器类,代表一个值存在或不存在
Optional.of(banana).filter(e -> e.getMoney() == 1).get();

// 三、java8 其他高阶操作:
// 1、科里化 􏱷
// 是一种􏳿将􏶞􏵕2个参数(比如,x和y)的函数f转􏷊化为使用一个参数的函数g,
// 并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。
// 后者的返回值和􏱍始函数的 返回值相􏱬,即f(x,y) = (g(x))(y)。
// 比如:单位转换通常都会􏸙及转换因子以及基线调整因子的问题。
DoubleUnaryOperator convertCtoF = curriedConverter(9.0 / 5, 32);
DoubleUnaryOperator convertUSDtoGBP = curriedConverter(0.6, 0);

convertCtoF.applyAsDouble(1D);
convertUSDtoGBP.applyAsDouble(1D);

// f(x)=ax+b
static DoubleUnaryOperator curriedConverter(double f, double b) {
return (double x) -> x * f + b;
}


/**
* @desc: 自定义Collector 将Stream<T>中的所有元素收集到一个 List<T>里
*/
public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {
// 建立􏾒的结果容器
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}

//将􏵐􏷷元素添加到结果容器
@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
// 合并􏲫两个结果容容器
@Override
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1;
};
}

//􏴮对结果容器应用的最终转换
@Override
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}

@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(
IDENTITY_FINISH, CONCURRENT));
}
}

三、默认⽅方法:

它提供的能⼒力力能帮助类库的设计 者们定义新的操作,增强接⼝口的能⼒力力,它们︎蔽了了将来的变化对 ⽤用户的

影响.

比如:List类1.8新增的foreach⽅法

1
2
3
4
5
6
7
8
9
10
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

四、异步处理理

CompletableFuture

CompletableFuture提供了了像thenCompose、thenCombine、allOf这样的 操作,对Future︎及的通⽤用设
计模式提供了了函数式编程的细︎度控制,有助于避免使⽤用 命令式编程的模︎代码。

五、时间处理理类

LocalDateLocalTimeInstantDuration 以︎及 Period 极⼤简化⽅便了日期处理⽅式,包括:

  • Java 8之前⽼版的java.util.Date类以及其他⽤于建模⽇期时间的类有很多不⼀致及 设计上的缺,包括易变性以及的值、默认值和命名。
  • 新版的⽇期和时间API中,⽇期时间对象是不可变的。
  • 新的API提供了两种不同的时间表示⽅式,有效地区分了运⾏时⼈和机器的不同需求。
  • 可以⽤绝对或者相对的⽅式操⽇期和时间,操作的结果总是返回⼀个新的实例,⽼的⽇期时间对象不会发⽣变化。
  • TemporalAdjuster让你能够⽤更精细的⽅式操⽇期,不再限于⼀次只能改变它的 ⼀个值,并且你还可按照需求定义⾃⼰的⽇期转换器。 你现在可以按照特定的格式需求,定义⾃⼰的格式器,打印输出或者解⽇期时间对象。 这些格式器可以通过模创建,也可以⾃⼰编程创建,并且它们都是线程安全的。
  • 可以⽤相对于某个地区/位的⽅式,或者以与UTC/格尼时间的绝对差的⽅式表 示时区,并将其应⽤到⽇期时间对象上,对其进⾏本地化。
  • 可以使⽤不同于ISO-8601标准系统的其他⽇历系统

六、why java8?

函数式编程:

  • 实现和维护系统,无需担心锁引起的各种问题,充分发︎系统的并发能力;
  • 共享的可变数据,︎︎纯粹且⽆作用.在完全无锁的情况下,使⽤用多核的并发机制;
  • 下⾯面是这⼀章中你应该掌握的关键概念。
  • 从⻓远看,︎少共享的可变数据结构能帮助你︎低维护和调︎程序的代价。
  • 函数式编程⽀持⽆副作⽤的方法和声明式编程。
  • 函数式方法可以由它的输入参数及输出结果进行︎断。
  • 如果一个函数使用相同的参数值调用,总是返回相同的结果,那么它是引⽤用透明的。采用递︎可以取得迭代式的结构,⽐比如while循环。
  • 相对于Java语言中传统的递︎,“︎︎递”可能是⼀种更好的⽅式,它开启了一︎扇门,让我们有机会最终使用编译器进行优化。

七、函数编程技巧

  • ⾼高︎阶函数:
1
2
3
Function<String, String> transformationPipeline
= addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
  • 函数科⾥化(⻅上⽂demo)

  • Stream 延迟计算 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 // 延迟计算:
//1是集合类持有的所有元素都是存储在内存中的,非常巨大的集合类会占用大量的内存,
// 而Stream的元素却是在访问的时候才被计算出来,这种“延迟计算”的特性有点类似Clojure的lazy-seq,占用内存很少。
//2是集合类的迭代逻辑是调用者负责,通常是for循环,而Stream的迭代是隐含在对Stream的各种操作中,例如map()。
Stream.generate(new Supplier<Long>() {
//反复调用get(),将得到一个无穷数列,利用这个Supplier,基于生成流,可以创建一个无穷的Stream
long value = 0;

@Override
public Long get() {
this.value = this.value + 1;
return this.value;
}
}).map((x) -> x * x).limit(10).forEach(System.out::println);
//对这个Stream做任何map()、filter()等操作都是完全可以的,
// 这说明Stream API对Stream进行转换并生成一个新的Stream并非实时计算,而是做了延迟计算。

Stream<Long> natural = Stream.generate(new NaturalSupplier());
natural.map((x) -> {
return x * x;
}).limit(10).forEach(System.out::println);

  • 模式匹配? Java8⽀持的不太好,感觉作⽤不太⼤,代替switch case

  • 实现缓存记忆表: ⽐如Map::computeIfAbsent()⽅法

1
2
3
4
5
6
7
// cache类似DP算法中的记录薄,基于function,computeIfAbsent每次会将表达式计算结果暂存,提升递归性能,非常的优雅, // 同样的实现,在java8前会很麻烦
static int fibonacciJava8(int n) {
return (int) cache.computeIfAbsent(n, (key) -> {
System.out.println("calculating FibonacciJava8 " + n);
return fibonacciJava8(n - 2) + fibonacciJava8(n - 1);
});
}
  • 结合器 ⽐如:CompletableFuture::thenCombine(),该⽅法接受两个CompletableFuture⽅法 和⼀个BiFunction⽅法,返回 另⼀个CompletableFuture⽅法。
1
2
3
4
demo: 
static Function compose(Function g, Function f) {
return x -> g.apply(f.apply(x));
}
  • 其他:

    1
    2
    3
    4
    5
    6
    7
        // 4、骚操作:实现一个失败后重试最多5次的代码
    Stream.generate(() -ss> Math.random() > 0.8 ? "ok" : null)
    .limit(5)
    .filter(Objects::nonNull)
    .findFirst()
    .ifPresent(System.out::println);
    }
  • Idea ⾃带Refactor Function 骚操作:

    image-20211102194326898

八、超越java 8—jdk版本重大特性总结

Java 8 : lambda 函数式计算、 stream 、 JVM 废弃永久代 , 引入 metaSpace 区

Java9 : Jigsaw 模块系统 ( 打包模块化 )

增强了 Stream , Optional , Process API

新增 HTTP2 Client

Java 10: 新增局部类型推断 var

Java 11 : Lambda 表达式中使用 var , 引入 ZGC( 实验功能 )

Java 12 : switch 表达式扩展 , 增强 g1 ( 自动返回未使用堆给操作系统 )

java 13 : 增强 ZGC( 释放未使用内存 ) 、引入文本块功能

Java 14: 移除CMS收集器

Java 15(2020-9-15 发布 ): ZGC 、 Shenandoah 垃圾回器 从实验功能变为产品功能

九、结论总结

1 、行为参数化(Lambda以及方法引用)

这些值具有类似Function<T, R>、Predicate或者BiFunction<T, U, R>这样的类型,值的接收方可以通过
apply、test或其他类似的⽅法执行这些方法。Lambda表达式自身是 一个相当酷︎的概念,不不过Java 8对
它们的使用方式——将它们与全新的Stream API相结合,最终把它们推向了了新一代Java的核⼼。

2 、流

它采用︎︎算法将这些操作组成一个流水线,通过单次流遍历, 一次性完成所有的操作。

它的parallel⽅法能帮助将一个Stream标记为适合进⾏并⾏处理。

3 、 CompletableFuture

CompletableFuture提供了了像thenCompose、thenCombine、allOf这样的 操作,对Future︎及的通⽤用设
计模式提供了了函数式编程的细︎度控制,有助于避免使用命令式编程的模︎代码。

4 、optional

如果在程序中始终如一地使用Optional,你的应⽤应该永远不会发⽣生NullPointerException异常。

5 、默认⽅法

它提供的能力能帮助类库的设计者们定义新的操作,增强接口的能力,它们︎蔽了将来的变化对用户的
影响.

十、附录

Lambda表︎式和JVM︎字节码

编译时,匿匿名类和Lambda表达式使⽤了不不同的字节码指令。(javap -c -v ClassName)

Lambda 创建额外的类 现在被invokedynamic指令替代了了。这种方式使⽤用 invokedynamic ,可以将实现
Lambda表达式的这部分代码的字节码⽣成 推︎到运行时。这种情况下,编译器可以生成 ⼀一个方法,该⽅
法含有该Lambda表达式同样的签名.

参考:

流介绍: https://www.edjdhbb.com/2019/02/23/java-8-stream%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86/

《Java8 实战》https://github.com/zxiaofan/JavaBooks/blob/master/Java%208%E5%AE%9E%E6%88%98.pdf