1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

Update java8-common-new-features.md

This commit is contained in:
guide 2021-01-28 15:01:18 +08:00
parent a1e51284af
commit a69ce46bc0

View File

@ -1,19 +1,21 @@
# 作为一个老程序员,我从来不用 Java 8 新特
# 作为一个老程序员,我从来不用 Java 8 新特
Oracle 于 2014 发布了 Java8jdk1.8),诸多原因使它成为目前市场上使用最多的 jdk 版本。虽然发布距今已将近 7 年,但很多程序员对其新特还是不够了解,尤其是用惯了 java8 之前版本的老程序员,比如我。
Oracle 于 2014 发布了 Java8jdk1.8),诸多原因使它成为目前市场上使用最多的 jdk 版本。虽然发布距今已将近 7 年,但很多程序员对其新特还是不够了解,尤其是用惯了 java8 之前版本的老程序员,比如我。
为了不脱离队伍太远,还是有必要对这些新特做一些总结梳理。它较 jdk.7 有很多变化或者说是优化,比如 interface 里可以有静态方法,并且可以有方法体,这一点就颠覆了之前的认知;`java.util.HashMap`数据结构里增加了红黑树;还有众所周知的 Lambda 表达式等等。本文不能把所有的新特征都给大家一一分享,只列出比较常用的新特征给大家做详细讲解。[更多的新特征看官网](https://www.oracle.com/java/technologies/javase/8-whats-new.html)
为了不脱离队伍太远,还是有必要对这些新特做一些总结梳理。它较 jdk.7 有很多变化或者说是优化,比如 interface 里可以有静态方法,并且可以有方法体,这一点就颠覆了之前的认知;`java.util.HashMap` 数据结构里增加了红黑树;还有众所周知的 Lambda 表达式等等。本文不能把所有的新特性都给大家一一分享,只列出比较常用的新特性给大家做详细讲解。更多相关内容请看[官网关于 Java8 的新特性的介绍](https://www.oracle.com/java/technologies/javase/8-whats-new.html)
## Interface
interface 的设计初衷是面向抽象提高扩展性。这也留有一点遗憾Interface 修改的时候,实现它的类也必须跟着改。为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用`default``static`修饰,这样就可以有方法体,实现类也不必重写此方法。
interface 的设计初衷是面向抽象提高扩展性。这也留有一点遗憾Interface 修改的时候,实现它的类也必须跟着改。
为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用`default``static`修饰,这样就可以有方法体,实现类也不必重写此方法。
一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。
1. `default`修饰的方法,是普通实例方法,可以用`this`调用,可以被子类继承、重写。
2. `static`修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用`Interface`调用。
看一个接口代码实例
我们来看一个实际的子。
```java
public interface InterfaceNew {
@ -41,7 +43,7 @@ public interface InterfaceNew1 {
}
```
如果有一个类既实现了 InterfaceNew 又实现了 InterfaceNew1它们都有`def()`,这时就必须重写`def()`
如果有一个类既实现了 `InterfaceNew` 接口又实现了 `InterfaceNew1`接口,它们都有`def()`并且 `InterfaceNew` 接口和 `InterfaceNew1`接口没有继承关系的话,这时就必须重写`def()`不然的话,编译的时候就会报错。
```java
public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
@ -61,9 +63,13 @@ public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
}
```
### interface & abstract class 区别
但是,如果 `InterfaceNew1` 接口实现了`InterfaceNew` 接口的话,就不需要重写 `def()`
既然 interface 也可以有自己的方法实现,似乎和 abstract class 没多大区别了。其实它们还是有区别的
**在 Java 8 ,接口和抽象类有什么区别的?**
很多小伙伴认为:“既然 interface 也可以有自己的方法实现,似乎和 abstract class 没多大区别了。”
其实它们还是有区别的
1. interface 和 class 的区别,好像是废话,主要有
@ -72,7 +78,7 @@ public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
2. interface 的方法是更像是一个扩展插件。而 abstract class 的方法是要继承的。
开始我们也提到interface 新增`default`,和`static`修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代`abstract class`。在使用上,该用 abstract class 的地方还是要用 abstract class不要因为 interface 的新特而降之替换。
开始我们也提到interface 新增`default`,和`static`修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代`abstract class`。在使用上,该用 abstract class 的地方还是要用 abstract class不要因为 interface 的新特而降之替换。
**记住接口永远和类不一样。**
@ -80,11 +86,11 @@ public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
**定义**:也称 SAM 接口,即 Single Abstract Method interfaces有且只有一个抽象方法但可以有多个非抽象方法的接口。
在 java 8 中专门有一个包放函数式接口`java.util.function`,该包下的所有接口都有*@FunctionalInterface*注解,提供函数式编程。
在 java 8 中专门有一个包放函数式接口`java.util.function`,该包下的所有接口都有 `@FunctionalInterface` 注解,提供函数式编程。
在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有
在其他包中也有函数式接口,其中一些没有`@FunctionalInterface` 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有
@FunctionalInterface 注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
`@FunctionalInterface`注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
## Lambda 表达式
@ -109,7 +115,7 @@ public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
过去给方法传动态参数的唯一方法是使用内部类。比如
1. Runnable 接口
**1.`Runnable` 接口**
```java
new Thread(new Runnable() {
@ -122,7 +128,7 @@ new Thread(new Runnable() {
new Thread(() -> System.out.println("It's a lambda function!")).start();
```
2. Comperator 接口
**2.`Comperator` 接口**
```java
List<Integer> strings = Arrays.asList(1, 2, 3);
@ -140,7 +146,7 @@ Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);
```
3. Listener 接口
**3.`Listener` 接口**
```java
JButton button = new JButton();
@ -154,7 +160,7 @@ public void itemStateChanged(ItemEvent e) {
button.addItemListener(e -> e.getItem());
```
4. 自定义接口
**4.自定义接口**
上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的**函数式接口**,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。
@ -252,11 +258,11 @@ lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属
## Stream
java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如`java.io.FileInputStream`,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何*CRUD*。
java 新增了 `java.util.stream` 包,它和之前的流大同小异。之前接触最多的是资源流,比如`java.io.FileInputStream`,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何*CRUD*。
`Stream`依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。
它的源数据可以是 Collection、Array 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
它的源数据可以是 `Collection``Array` 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
### 流类型
@ -454,9 +460,9 @@ Predicate.test 执行
Predicate.test 执行
```
按执行顺序应该是先打印 4 次「Predicate.test 执行」再打印「count 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用`count()`方法后才执行。
按执行顺序应该是先打印 4 次「`Predicate.test` 执行」,再打印「`count` 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用`count()`方法后才执行。
上面都是串行 Stream 的实例。并行 parallelStream 在使用方法上和串行一样。主要区别是 parallelStream 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 ForkJoin 框架和 ForkJoinPool。这里可以简单的理解它是通过线程池来实现的这样就会涉及到线程安全线程消耗等问题。下面我们通过代码来体验一下串行流的多线程执行。
上面都是串行 Stream 的实例。并行 parallelStream 在使用方法上和串行一样。主要区别是 parallelStream 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 `ForkJoin` 框架和 `ForkJoinPool`。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下串行流的多线程执行。
```java
@Test
@ -484,7 +490,7 @@ ForkJoinPool.commonPool-worker-9>>2
## Optional
在[阿里巴巴开发手册](https://share.weiyun.com/ThuqEbD5)中这样写到:
在[阿里巴巴开发手册关于 Optional 的介绍](https://share.weiyun.com/ThuqEbD5)中这样写到:
> 防止 NPE是程序员的基本修养注意 NPE 产生的场景:
>
@ -504,9 +510,9 @@ ForkJoinPool.commonPool-worker-9>>2
>
> 正例:使用 JDK8 的 Optional 类来防止 NPE 问题。
他建议使用 Optional 解决 NPEjava.lang.NumberFormatException问题它就是为 NPE 而生的,其中可以包含空值或非空值。下面我们通过源码逐步揭开 Optional 的红盖头。
他建议使用 `Optional` 解决 NPE`java.lang.NumberFormatException`)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。下面我们通过源码逐步揭开 `Optional` 的红盖头。
假设有一个 Zoo 类,里面有个属性 Dog需求要获取 Dog 的 age。
假设有一个 `Zoo` 类,里面有个属性 `Dog`,需求要获取 `Dog``age`
```java
class Zoo {
@ -533,7 +539,7 @@ if(zoo != null){
层层判断对象分空,有人说这种方式很丑陋不优雅,我并不这么认为。反而觉得很整洁,易读,易懂。你们觉得呢?
Optional 是这样的实现的
`Optional` 是这样的实现的
```java
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
@ -593,9 +599,9 @@ public static <T> T requireNonNull(T obj) {
}
```
*ofNullable*方法和*of*方法唯一区别就是当 value 为 null 时,*ofNullable*返回的是`EMPTY`of 会抛出*NullPointerException*异常。如果需要把*NullPointerException*暴漏出来就用*of*,否则就用*ofNullable*
`ofNullable` 方法和`of`方法唯一区别就是当 value 为 null 时,`ofNullable` 返回的是`EMPTY`of 会抛出 `NullPointerException` 异常。如果需要把 `NullPointerException` 暴漏出来就用 `of`,否则就用 `ofNullable`
### *map()*相关方法。
### `map()`相关方法。
```java
/**
@ -622,9 +628,9 @@ public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
}
```
*map()*和*flatMap()*的区别是
**`map()``flatMap()` 有什么区别的?**
1. 参数不一样map 的参数上面看到过flatMap 的参数是这样
**1.参数不一样,`map` 的参数上面看到过,`flatMap` 的参数是这样**
```java
class ZooFlat {
@ -648,7 +654,7 @@ public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
);
```
2. _flatMap()_ 参数返回值如果是 null 会抛 NullPointerException而*map()*返回`EMPTY`
**2.`flatMap()` 参数返回值如果是 null 会抛 `NullPointerException`,而 `map()` 返回`EMPTY`。**
### 判断 value 是否为 null
@ -726,7 +732,7 @@ public Optional<T> filter(Predicate<? super T> predicate) {
### 小结
看完 Optional 源码Optional 的方法真的非常简单,值得注意的是如果坚决不想看见 NPE就不要用*of(.)* 、_get()_、flatMap(..)\*。最后再综合用一下 Optional 的高频方法。
看完 `Optional` 源码,`Optional` 的方法真的非常简单,值得注意的是如果坚决不想看见 `NPE`,就不要用 `of() ``get()``flatMap(..)`\。最后再综合用一下 `Optional` 的高频方法。
```java
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3);
@ -745,7 +751,7 @@ Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==
### java.time 主要类
_java.util.Date_ 既包含日期又包含时间,而 _java.time_ 把它们进行了分离
`java.util.Date` 既包含日期又包含时间,而 `java.time` 把它们进行了分离
```java
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
@ -755,7 +761,7 @@ LocalTime.class //时间 format: HH:mm:ss
### 格式化
- 老 Date
**Java 8 之前:**
```java
public void oldFormat(){
@ -777,7 +783,7 @@ public void oldFormat(){
}
```
- 新特征
**Java 8 之后:**
```java
public void newFormat(){
@ -798,7 +804,7 @@ public void newFormat(){
### 字符串转日期格式
- 老 Date
**Java 8 之前:**
```java
//已弃用
@ -808,7 +814,7 @@ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2021-01-26");
```
- 新特征
**Java 8 之后:**
```java
LocalDate date = LocalDate.of(2021, 1, 26);
@ -821,15 +827,13 @@ LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");
```
老 Date 转换都需要借助 format 类,而新特征只需要*LocalDate、LocalTime、LocalDateTime*的 _of_ 或 *parse*方法。
**Java 8 之前** 转换都需要借助 `SimpleDateFormat` 类,而**Java 8 之后**只需要 `LocalDate``LocalTime``LocalDateTime``of``parse` 方法。
### 日期计算
- 一周后日期
下面仅以**一周后日期**为例其他单位年、月、日、1/2 日、时等等)大同小异。另外,这些单位都在 _java.time.temporal.ChronoUnit_ 枚举中定义。
当然还有以年、月、日、1/2 日、时、分、秒、毫秒、微妙、纳秒,甚至还有十年、百年、千年、世纪、永恒来计算的。这些单位都在 _java.time.temporal.ChronoUnit_ 枚举中定义,除了加运算当然还有减运算,下面仅以**一周后日期**为例,其他单位大同小异。
- 老 Date
**Java 8 之前:**
```java
public void afterDay(){
@ -853,7 +857,7 @@ LocalTime.parse("12:12:22");
}
```
- 新特征
**Java 8 之后:**
```java
public void pushWeek(){
@ -886,7 +890,7 @@ LocalTime.parse("12:12:22");
除了日期计算繁琐,获取特定一个日期也很麻烦,比如获取本月最后一天,第一天。
- 老 Date
**Java 8 之前:**
```java
public void getDay() {
@ -916,7 +920,7 @@ public void getDay() {
}
```
- 新特征
**Java 8 之后:**
```java
public void getDayNew() {
@ -934,23 +938,23 @@ public void getDayNew() {
}
```
_java.time.temporal.TemporalAdjusters_ 里面还有很多便捷的算法,这里就不带大家看 Api 了,都很简单,看了秒懂。
`java.time.temporal.TemporalAdjusters` 里面还有很多便捷的算法,这里就不带大家看 Api 了,都很简单,看了秒懂。
### JDBC 和 java8
现在 jdbc 时间类型和 java8 时间类型对应关系是
1. date ---> LocalDate
2. Time ---> LocalTime
3. timestamp ---> LocalDateTime
1. `Date` ---> `LocalDate`
2. `Time` ---> `LocalTime`
3. `TimesSamp` ---> `LocalDateTime`
而之前统统对应 Date也只有 Date。
而之前统统对应 `Date`,也只有 `Date`
### 时区
> 时区:正式的时区划分为每隔经度 15° 划分一个时区,全球共 24 个时区,每个时区相差 1 小时。但为了行政上的方便,常将 1 个国家或 1 个省份划在一起,比如我国幅员宽广,大概横跨 5 个时区,实际上只用东八时区的标准时即北京时间为准。
java.util.Date 对象实质上存的是 1970 年 1 月 1 日 0 点( GMT至 Date 对象所表示时刻所经过的毫秒数。也就是说不管在哪个时区 new Date它记录的毫秒数都一样和时区无关。但在使用上应该把它转换成当地时间这就涉及到了时间的国际化。java.util.Date 本身并不支持国际化,需要借助 TimeZone。
`java.util.Date` 对象实质上存的是 1970 年 1 月 1 日 0 点( GMT至 Date 对象所表示时刻所经过的毫秒数。也就是说不管在哪个时区 new Date它记录的毫秒数都一样和时区无关。但在使用上应该把它转换成当地时间这就涉及到了时间的国际化。`java.util.Date` 本身并不支持国际化,需要借助 `TimeZone`
```java
//北京时间Wed Jan 27 14:05:29 CST 2021
@ -971,7 +975,7 @@ System.out.println(date);
//Wed Jan 27 14:05:29 CST 2021
```
在新特征中引入了 _java.time.ZonedDateTime_ 来表示带时区的时间。它可以看成是*LocalDateTime + ZoneId*
在新特性中引入了 `java.time.ZonedDateTime ` 来表示带时区的时间。它可以看成是 `LocalDateTime + ZoneId`
```java
//当前时区时间
@ -1000,11 +1004,11 @@ System.out.println("本地时区时间: " + localZoned);
### 小结
通过上面比较新老 Date 的不同,当然只列出部分功能上的区别,更多功能还得自己去挖掘。总之 date-time-api 给日期操作带来了福利。在日常工作中遇到 date 类型的操作,第一考虑的是 date-time-api实在解决不了再考虑老的 Date。
通过上面比较新老 `Date` 的不同,当然只列出部分功能上的区别,更多功能还得自己去挖掘。总之 date-time-api 给日期操作带来了福利。在日常工作中遇到 date 类型的操作,第一考虑的是 date-time-api实在解决不了再考虑老的 Date。
## 总结
我们梳理总结的 java 8 新特
我们梳理总结的 java 8 新特
- Interface & functional Interface
- Lambda
@ -1012,4 +1016,4 @@ System.out.println("本地时区时间: " + localZoned);
- Optional
- Date time-api
这些都是开发当中比较常用的特征。梳理下来发现它们真香,而我却没有更早的应用。总觉得学习 java 8 新特征比较麻烦,一致使用老的实现方式。其实这些新特征几天就可以掌握一但掌握效率会有很大的提高。其实我们涨工资也是涨的学习的钱不学习终究会被淘汰35 岁危机会提前来临。
这些都是开发当中比较常用的特征。梳理下来发现它们真香,而我却没有更早的应用。总觉得学习 java 8 新特性比较麻烦,一致使用老的实现方式。其实这些新特性几天就可以掌握一但掌握效率会有很大的提高。其实我们涨工资也是涨的学习的钱不学习终究会被淘汰35 岁危机会提前来临。