2023-08-05 15:55:16 +08:00
|
|
|
|
---
|
|
|
|
|
title: Java8 新特性实战
|
|
|
|
|
category: Java
|
|
|
|
|
tag:
|
|
|
|
|
- Java新特性
|
|
|
|
|
---
|
2021-02-10 18:35:29 +08:00
|
|
|
|
|
|
|
|
|
> 本文来自[cowbi](https://github.com/cowbi)的投稿~
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2022-03-01 18:16:51 +08:00
|
|
|
|
Oracle 于 2014 发布了 Java8(jdk1.8),诸多原因使它成为目前市场上使用最多的 jdk 版本。虽然发布距今已将近 7 年,但很多程序员对其新特性还是不够了解,尤其是用惯了 Java8 之前版本的老程序员,比如我。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
为了不脱离队伍太远,还是有必要对这些新特性做一些总结梳理。它较 jdk.7 有很多变化或者说是优化,比如 interface 里可以有静态方法,并且可以有方法体,这一点就颠覆了之前的认知;`java.util.HashMap` 数据结构里增加了红黑树;还有众所周知的 Lambda 表达式等等。本文不能把所有的新特性都给大家一一分享,只列出比较常用的新特性给大家做详细讲解。更多相关内容请看[官网关于 Java8 的新特性的介绍](https://www.oracle.com/java/technologies/javase/8-whats-new.html)。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
## Interface
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
interface 的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface 修改的时候,实现它的类也必须跟着改。
|
|
|
|
|
|
|
|
|
|
为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用`default` 或 `static`修饰,这样就可以有方法体,实现类也不必重写此方法。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
一个 interface 中可以有多个方法被它们修饰,这 2 个修饰符的区别主要也是普通方法和静态方法的区别。
|
|
|
|
|
|
|
|
|
|
1. `default`修饰的方法,是普通实例方法,可以用`this`调用,可以被子类继承、重写。
|
|
|
|
|
2. `static`修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用`Interface`调用。
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
我们来看一个实际的例子。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public interface InterfaceNew {
|
|
|
|
|
static void sm() {
|
|
|
|
|
System.out.println("interface提供的方式实现");
|
|
|
|
|
}
|
|
|
|
|
static void sm2() {
|
|
|
|
|
System.out.println("interface提供的方式实现");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default void def() {
|
|
|
|
|
System.out.println("interface default方法");
|
|
|
|
|
}
|
|
|
|
|
default void def2() {
|
|
|
|
|
System.out.println("interface default2方法");
|
|
|
|
|
}
|
|
|
|
|
//须要实现类重写
|
|
|
|
|
void f();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface InterfaceNew1 {
|
|
|
|
|
default void def() {
|
|
|
|
|
System.out.println("InterfaceNew1 default方法");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
如果有一个类既实现了 `InterfaceNew` 接口又实现了 `InterfaceNew1`接口,它们都有`def()`,并且 `InterfaceNew` 接口和 `InterfaceNew1`接口没有继承关系的话,这时就必须重写`def()`。不然的话,编译的时候就会报错。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
InterfaceNewImpl interfaceNew = new InterfaceNewImpl();
|
|
|
|
|
interfaceNew.def();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void def() {
|
|
|
|
|
InterfaceNew1.super.def();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void f() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**在 Java 8 ,接口和抽象类有什么区别的?**
|
|
|
|
|
|
|
|
|
|
很多小伙伴认为:“既然 interface 也可以有自己的方法实现,似乎和 abstract class 没多大区别了。”
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
其实它们还是有区别的
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-02-06 17:59:01 +08:00
|
|
|
|
1. interface 和 class 的区别,好像是废话,主要有:
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
- 接口多实现,类单继承
|
|
|
|
|
- 接口的方法是 public abstract 修饰,变量是 public static final 修饰。 abstract class 可以用其他修饰符
|
|
|
|
|
|
|
|
|
|
2. interface 的方法是更像是一个扩展插件。而 abstract class 的方法是要继承的。
|
|
|
|
|
|
2021-08-25 17:32:35 +08:00
|
|
|
|
开始我们也提到,interface 新增`default`和`static`修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代`abstract class`。在使用上,该用 abstract class 的地方还是要用 abstract class,不要因为 interface 的新特性而将之替换。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
**记住接口永远和类不一样。**
|
|
|
|
|
|
|
|
|
|
## functional interface 函数式接口
|
|
|
|
|
|
|
|
|
|
**定义**:也称 SAM 接口,即 Single Abstract Method interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口。
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
在 java 8 中专门有一个包放函数式接口`java.util.function`,该包下的所有接口都有 `@FunctionalInterface` 注解,提供函数式编程。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
在其他包中也有函数式接口,其中一些没有`@FunctionalInterface` 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
`@FunctionalInterface`注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
## Lambda 表达式
|
|
|
|
|
|
2021-02-06 17:59:01 +08:00
|
|
|
|
接下来谈众所周知的 Lambda 表达式。它是推动 Java 8 发布的最重要新特性。是继泛型(`Generics`)和注解(`Annotation`)以来最大的变化。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的*函数式编程*。
|
|
|
|
|
|
|
|
|
|
> Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。
|
|
|
|
|
|
|
|
|
|
### 语法格式
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
(parameters) -> expression 或
|
|
|
|
|
(parameters) ->{ statements; }
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Lambda 实战
|
|
|
|
|
|
|
|
|
|
我们用常用的实例来感受 Lambda 带来的便利
|
|
|
|
|
|
|
|
|
|
#### 替代匿名内部类
|
|
|
|
|
|
|
|
|
|
过去给方法传动态参数的唯一方法是使用内部类。比如
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**1.`Runnable` 接口**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
new Thread(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
System.out.println("The runable now is using!");
|
|
|
|
|
}
|
|
|
|
|
}).start();
|
|
|
|
|
//用lambda
|
|
|
|
|
new Thread(() -> System.out.println("It's a lambda function!")).start();
|
|
|
|
|
```
|
|
|
|
|
|
2022-02-08 17:30:19 +08:00
|
|
|
|
**2.`Comparator` 接口**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
List<Integer> strings = Arrays.asList(1, 2, 3);
|
|
|
|
|
|
|
|
|
|
Collections.sort(strings, new Comparator<Integer>() {
|
|
|
|
|
@Override
|
|
|
|
|
public int compare(Integer o1, Integer o2) {
|
|
|
|
|
return o1 - o2;}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//Lambda
|
|
|
|
|
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
|
|
|
|
|
//分解开
|
2022-10-28 22:20:46 +08:00
|
|
|
|
Comparator<Integer> comparator = (Integer o1, Integer o2) -> o1 - o2;
|
|
|
|
|
Collections.sort(strings, comparator);
|
2021-01-28 13:06:20 +08:00
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**3.`Listener` 接口**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
JButton button = new JButton();
|
|
|
|
|
button.addItemListener(new ItemListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void itemStateChanged(ItemEvent e) {
|
|
|
|
|
e.getItem();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
//lambda
|
|
|
|
|
button.addItemListener(e -> e.getItem());
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**4.自定义接口**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的**函数式接口**,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
```java
|
|
|
|
|
@FunctionalInterface
|
|
|
|
|
public interface Comparator<T>{}
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
@FunctionalInterface
|
|
|
|
|
public interface Runnable{}
|
|
|
|
|
```
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
我们自定义一个函数式接口
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
```java
|
|
|
|
|
@FunctionalInterface
|
2021-07-29 17:19:17 +08:00
|
|
|
|
public interface LambdaInterface {
|
2021-01-28 15:01:18 +08:00
|
|
|
|
void f();
|
|
|
|
|
}
|
|
|
|
|
//使用
|
|
|
|
|
public class LambdaClass {
|
|
|
|
|
public static void forEg() {
|
|
|
|
|
lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
|
|
|
|
|
}
|
|
|
|
|
//函数式接口参数
|
|
|
|
|
static void lambdaInterfaceDemo(LambdaInterface i){
|
2022-09-16 16:59:50 +08:00
|
|
|
|
i.f();
|
2021-01-28 15:01:18 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
#### 集合迭代
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
void lamndaFor() {
|
|
|
|
|
List<String> strings = Arrays.asList("1", "2", "3");
|
|
|
|
|
//传统foreach
|
|
|
|
|
for (String s : strings) {
|
|
|
|
|
System.out.println(s);
|
|
|
|
|
}
|
|
|
|
|
//Lambda foreach
|
|
|
|
|
strings.forEach((s) -> System.out.println(s));
|
|
|
|
|
//or
|
|
|
|
|
strings.forEach(System.out::println);
|
2023-12-30 17:14:13 +08:00
|
|
|
|
//map
|
2021-01-28 13:06:20 +08:00
|
|
|
|
Map<Integer, String> map = new HashMap<>();
|
|
|
|
|
map.forEach((k,v)->System.out.println(v));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 方法的引用
|
|
|
|
|
|
|
|
|
|
Java 8 允许使用 `::` 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class LambdaClassSuper {
|
|
|
|
|
LambdaInterface sf(){
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-29 17:19:17 +08:00
|
|
|
|
public class LambdaClass extends LambdaClassSuper {
|
2021-01-28 13:06:20 +08:00
|
|
|
|
public static LambdaInterface staticF() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public LambdaInterface f() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void show() {
|
|
|
|
|
//1.调用静态函数,返回类型必须是functional-interface
|
|
|
|
|
LambdaInterface t = LambdaClass::staticF;
|
|
|
|
|
|
|
|
|
|
//2.实例方法调用
|
|
|
|
|
LambdaClass lambdaClass = new LambdaClass();
|
|
|
|
|
LambdaInterface lambdaInterface = lambdaClass::f;
|
|
|
|
|
|
|
|
|
|
//3.超类上的方法调用
|
|
|
|
|
LambdaInterface superf = super::sf;
|
|
|
|
|
|
|
|
|
|
//4. 构造方法调用
|
|
|
|
|
LambdaInterface tt = LambdaClassSuper::new;
|
2021-07-29 17:19:17 +08:00
|
|
|
|
}
|
2021-01-28 13:06:20 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 访问变量
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
int i = 0;
|
|
|
|
|
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - i);
|
|
|
|
|
//i =3;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属性,不能被修改,如果修改,编译时就报错。
|
|
|
|
|
|
|
|
|
|
## Stream
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
java 新增了 `java.util.stream` 包,它和之前的流大同小异。之前接触最多的是资源流,比如`java.io.FileInputStream`,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何*CRUD*。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
`Stream`依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
它的源数据可以是 `Collection`、`Array` 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### 流类型
|
|
|
|
|
|
|
|
|
|
1. stream 串行流
|
|
|
|
|
2. parallelStream 并行流,可多线程执行
|
|
|
|
|
|
|
|
|
|
### 常用方法
|
|
|
|
|
|
|
|
|
|
接下来我们看`java.util.stream.Stream`常用方法
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
/**
|
|
|
|
|
* 返回一个串行流
|
|
|
|
|
*/
|
|
|
|
|
default Stream<E> stream()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回一个并行流
|
|
|
|
|
*/
|
|
|
|
|
default Stream<E> parallelStream()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回T的流
|
|
|
|
|
*/
|
|
|
|
|
public static<T> Stream<T> of(T t)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回其元素是指定值的顺序流。
|
|
|
|
|
*/
|
|
|
|
|
public static<T> Stream<T> of(T... values) {
|
|
|
|
|
return Arrays.stream(values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
|
|
|
|
|
*/
|
|
|
|
|
Stream<T> filter(Predicate<? super T> predicate);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 此流的所有元素是否与提供的predicate匹配。
|
|
|
|
|
*/
|
|
|
|
|
boolean allMatch(Predicate<? super T> predicate)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 此流任意元素是否有与提供的predicate匹配。
|
|
|
|
|
*/
|
|
|
|
|
boolean anyMatch(Predicate<? super T> predicate);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回一个 Stream的构建器。
|
|
|
|
|
*/
|
|
|
|
|
public static<T> Builder<T> builder();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用 Collector对此流的元素进行归纳
|
|
|
|
|
*/
|
|
|
|
|
<R, A> R collect(Collector<? super T, A, R> collector);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回此流中的元素数。
|
|
|
|
|
*/
|
|
|
|
|
long count();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
|
|
|
|
|
*/
|
|
|
|
|
Stream<T> distinct();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 遍历
|
|
|
|
|
*/
|
|
|
|
|
void forEach(Consumer<? super T> action);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
|
|
|
|
|
*/
|
|
|
|
|
Stream<T> limit(long maxSize);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用于映射每个元素到对应的结果
|
|
|
|
|
*/
|
|
|
|
|
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据提供的 Comparator进行排序。
|
|
|
|
|
*/
|
|
|
|
|
Stream<T> sorted(Comparator<? super T> comparator);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
|
|
|
|
|
*/
|
|
|
|
|
Stream<T> skip(long n);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 返回一个包含此流的元素的数组。
|
|
|
|
|
*/
|
|
|
|
|
Object[] toArray();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
|
|
|
|
|
*/
|
|
|
|
|
<A> A[] toArray(IntFunction<A[]> generator);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 合并流
|
|
|
|
|
*/
|
|
|
|
|
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 实战
|
|
|
|
|
|
2021-02-06 17:59:01 +08:00
|
|
|
|
本文列出 `Stream` 具有代表性的方法之使用,更多的使用方法还是要看 Api。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Test
|
|
|
|
|
public void test() {
|
|
|
|
|
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
|
|
|
|
|
//返回符合条件的stream
|
|
|
|
|
Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s));
|
|
|
|
|
//计算流符合条件的流的数量
|
|
|
|
|
long count = stringStream.count();
|
|
|
|
|
|
|
|
|
|
//forEach遍历->打印元素
|
|
|
|
|
strings.stream().forEach(System.out::println);
|
|
|
|
|
|
|
|
|
|
//limit 获取到1个元素的stream
|
|
|
|
|
Stream<String> limit = strings.stream().limit(1);
|
|
|
|
|
//toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环
|
|
|
|
|
String[] array = limit.toArray(String[]::new);
|
|
|
|
|
|
|
|
|
|
//map 对每个元素进行操作返回新流
|
|
|
|
|
Stream<String> map = strings.stream().map(s -> s + "22");
|
|
|
|
|
|
|
|
|
|
//sorted 排序并打印
|
|
|
|
|
strings.stream().sorted().forEach(System.out::println);
|
|
|
|
|
|
|
|
|
|
//Collectors collect 把abc放入容器中
|
|
|
|
|
List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());
|
|
|
|
|
//把list转为string,各元素用,号隔开
|
|
|
|
|
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));
|
|
|
|
|
|
|
|
|
|
//对数组的统计,比如用
|
|
|
|
|
List<Integer> number = Arrays.asList(1, 2, 5, 4);
|
|
|
|
|
|
|
|
|
|
IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();
|
|
|
|
|
System.out.println("列表中最大的数 : "+statistics.getMax());
|
|
|
|
|
System.out.println("列表中最小的数 : "+statistics.getMin());
|
|
|
|
|
System.out.println("平均数 : "+statistics.getAverage());
|
|
|
|
|
System.out.println("所有数之和 : "+statistics.getSum());
|
|
|
|
|
|
|
|
|
|
//concat 合并流
|
|
|
|
|
List<String> strings2 = Arrays.asList("xyz", "jqx");
|
|
|
|
|
Stream.concat(strings2.stream(),strings.stream()).count();
|
|
|
|
|
|
|
|
|
|
//注意 一个Stream只能操作一次,不能断开,否则会报错。
|
|
|
|
|
Stream stream = strings.stream();
|
|
|
|
|
//第一次使用
|
|
|
|
|
stream.limit(2);
|
|
|
|
|
//第二次使用
|
|
|
|
|
stream.forEach(System.out::println);
|
|
|
|
|
//报错 java.lang.IllegalStateException: stream has already been operated upon or closed
|
|
|
|
|
|
|
|
|
|
//但是可以这样, 连续使用
|
|
|
|
|
stream.limit(2).forEach(System.out::println);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 延迟执行
|
|
|
|
|
|
2021-02-06 17:59:01 +08:00
|
|
|
|
在执行返回 `Stream` 的方法时,并不立刻执行,而是等返回一个非 `Stream` 的方法后才执行。因为拿到 `Stream` 并不能直接用,而是需要处理成一个常规类型。这里的 `Stream` 可以想象成是二进制流(2 个完全不一样的东东),拿到也看不懂。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-02-06 17:59:01 +08:00
|
|
|
|
我们下面分解一下 `filter` 方法。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Test
|
|
|
|
|
public void laziness(){
|
|
|
|
|
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
|
|
|
|
|
Stream<Integer> stream = strings.stream().filter(new Predicate() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean test(Object o) {
|
|
|
|
|
System.out.println("Predicate.test 执行");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
System.out.println("count 执行");
|
|
|
|
|
stream.count();
|
|
|
|
|
}
|
|
|
|
|
/*-------执行结果--------*/
|
|
|
|
|
count 执行
|
|
|
|
|
Predicate.test 执行
|
|
|
|
|
Predicate.test 执行
|
|
|
|
|
Predicate.test 执行
|
|
|
|
|
Predicate.test 执行
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
按执行顺序应该是先打印 4 次「`Predicate.test` 执行」,再打印「`count` 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用`count()`方法后才执行。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-08-25 17:32:35 +08:00
|
|
|
|
上面都是串行 `Stream` 的实例。并行 `parallelStream` 在使用方法上和串行一样。主要区别是 `parallelStream` 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 `ForkJoin` 框架和 `ForkJoinPool`。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下并行流的多线程执行。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Test
|
|
|
|
|
public void parallelStreamTest(){
|
|
|
|
|
List<Integer> numbers = Arrays.asList(1, 2, 5, 4);
|
|
|
|
|
numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
|
|
|
|
|
}
|
|
|
|
|
//执行结果
|
|
|
|
|
main>>5
|
|
|
|
|
ForkJoinPool.commonPool-worker-2>>4
|
|
|
|
|
ForkJoinPool.commonPool-worker-11>>1
|
|
|
|
|
ForkJoinPool.commonPool-worker-9>>2
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
从结果中我们看到,for-each 用到的是多线程。
|
|
|
|
|
|
|
|
|
|
### 小结
|
|
|
|
|
|
|
|
|
|
从源码和实例中我们可以总结出一些 stream 的特点
|
|
|
|
|
|
|
|
|
|
1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
|
|
|
|
|
2. 方法参数都是函数式接口类型
|
|
|
|
|
3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
|
|
|
|
|
4. Stream 不保存数据,不改变数据源
|
|
|
|
|
|
|
|
|
|
## Optional
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
在[阿里巴巴开发手册关于 Optional 的介绍](https://share.weiyun.com/ThuqEbD5)中这样写到:
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
> 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
|
|
|
|
|
>
|
|
|
|
|
> 1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
|
|
|
|
|
>
|
|
|
|
|
> 反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
|
|
|
|
|
>
|
|
|
|
|
> 2) 数据库的查询结果可能为 null。
|
|
|
|
|
>
|
|
|
|
|
> 3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
|
|
|
|
|
>
|
|
|
|
|
> 4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
|
|
|
|
|
>
|
|
|
|
|
> 5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
|
|
|
|
|
>
|
|
|
|
|
> 6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
|
|
|
|
|
>
|
|
|
|
|
> 正例:使用 JDK8 的 Optional 类来防止 NPE 问题。
|
|
|
|
|
|
2021-06-08 17:29:29 +08:00
|
|
|
|
他建议使用 `Optional` 解决 NPE(`java.lang.NullPointerException`)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。下面我们通过源码逐步揭开 `Optional` 的红盖头。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
假设有一个 `Zoo` 类,里面有个属性 `Dog`,需求要获取 `Dog` 的 `age`。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
class Zoo {
|
|
|
|
|
private Dog dog;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Dog {
|
|
|
|
|
private int age;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
传统解决 NPE 的办法如下:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
Zoo zoo = getZoo();
|
|
|
|
|
if(zoo != null){
|
|
|
|
|
Dog dog = zoo.getDog();
|
|
|
|
|
if(dog != null){
|
|
|
|
|
int age = dog.getAge();
|
|
|
|
|
System.out.println(age);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-06-08 21:39:04 +08:00
|
|
|
|
层层判断对象非空,有人说这种方式很丑陋不优雅,我并不这么认为。反而觉得很整洁,易读,易懂。你们觉得呢?
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
`Optional` 是这样的实现的:
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
|
|
|
|
|
System.out.println(age)
|
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
是不是简洁了很多呢?
|
|
|
|
|
|
|
|
|
|
### 如何创建一个 Optional
|
|
|
|
|
|
|
|
|
|
上例中`Optional.ofNullable`是其中一种创建 Optional 的方式。我们先看一下它的含义和其他创建 Optional 的源码方法。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
/**
|
|
|
|
|
* Common instance for {@code empty()}. 全局EMPTY对象
|
|
|
|
|
*/
|
|
|
|
|
private static final Optional<?> EMPTY = new Optional<>();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optional维护的值
|
|
|
|
|
*/
|
|
|
|
|
private final T value;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果value是null就返回EMPTY,否则就返回of(T)
|
|
|
|
|
*/
|
|
|
|
|
public static <T> Optional<T> ofNullable(T value) {
|
|
|
|
|
return value == null ? empty() : of(value);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 返回 EMPTY 对象
|
|
|
|
|
*/
|
|
|
|
|
public static<T> Optional<T> empty() {
|
|
|
|
|
Optional<T> t = (Optional<T>) EMPTY;
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 返回Optional对象
|
|
|
|
|
*/
|
|
|
|
|
public static <T> Optional<T> of(T value) {
|
|
|
|
|
return new Optional<>(value);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 私有构造方法,给value赋值
|
|
|
|
|
*/
|
|
|
|
|
private Optional(T value) {
|
|
|
|
|
this.value = Objects.requireNonNull(value);
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 所以如果of(T value) 的value是null,会抛出NullPointerException异常,这样貌似就没处理NPE问题
|
|
|
|
|
*/
|
|
|
|
|
public static <T> T requireNonNull(T obj) {
|
|
|
|
|
if (obj == null)
|
|
|
|
|
throw new NullPointerException();
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
`ofNullable` 方法和`of`方法唯一区别就是当 value 为 null 时,`ofNullable` 返回的是`EMPTY`,of 会抛出 `NullPointerException` 异常。如果需要把 `NullPointerException` 暴漏出来就用 `of`,否则就用 `ofNullable`。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
**`map()` 和 `flatMap()` 有什么区别的?**
|
|
|
|
|
|
|
|
|
|
`map` 和 `flatMap` 都是将一个函数应用于集合中的每个元素,但不同的是`map`返回一个新的集合,`flatMap`是将每个元素都映射为一个集合,最后再将这个集合展平。
|
|
|
|
|
|
|
|
|
|
在实际应用场景中,如果`map`返回的是数组,那么最后得到的是一个二维数组,使用`flatMap`就是为了将这个二维数组展平变成一个一维数组。
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
|
|
|
|
```java
|
2023-08-05 15:33:21 +08:00
|
|
|
|
public class MapAndFlatMapExample {
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
List<String[]> listOfArrays = Arrays.asList(
|
|
|
|
|
new String[]{"apple", "banana", "cherry"},
|
|
|
|
|
new String[]{"orange", "grape", "pear"},
|
|
|
|
|
new String[]{"kiwi", "melon", "pineapple"}
|
|
|
|
|
);
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
List<String[]> mapResult = listOfArrays.stream()
|
|
|
|
|
.map(array -> Arrays.stream(array).map(String::toUpperCase).toArray(String[]::new))
|
|
|
|
|
.collect(Collectors.toList());
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
System.out.println("Using map:");
|
2024-03-02 16:46:26 +08:00
|
|
|
|
mapResult.forEach(arrays-> System.out.println(Arrays.toString(arrays)));
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
List<String> flatMapResult = listOfArrays.stream()
|
|
|
|
|
.flatMap(array -> Arrays.stream(array).map(String::toUpperCase))
|
|
|
|
|
.collect(Collectors.toList());
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
System.out.println("Using flatMap:");
|
|
|
|
|
System.out.println(flatMapResult);
|
2021-01-28 15:01:18 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
```
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
运行结果:
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
2023-10-08 16:33:50 +08:00
|
|
|
|
```plain
|
2023-08-05 15:33:21 +08:00
|
|
|
|
Using map:
|
|
|
|
|
[[APPLE, BANANA, CHERRY], [ORANGE, GRAPE, PEAR], [KIWI, MELON, PINEAPPLE]]
|
|
|
|
|
|
|
|
|
|
Using flatMap:
|
|
|
|
|
[APPLE, BANANA, CHERRY, ORANGE, GRAPE, PEAR, KIWI, MELON, PINEAPPLE]
|
|
|
|
|
```
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
最简单的理解就是`flatMap()`可以将`map()`的结果展开。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2023-08-05 15:33:21 +08:00
|
|
|
|
在`Optional`里面,当使用`map()`时,如果映射函数返回的是一个普通值,它会将这个值包装在一个新的`Optional`中。而使用`flatMap`时,如果映射函数返回的是一个`Optional`,它会将这个返回的`Optional`展平,不再包装成嵌套的`Optional`。
|
|
|
|
|
|
|
|
|
|
下面是一个对比的示例代码:
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
|
|
|
|
```java
|
2023-08-05 15:33:21 +08:00
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
int userId = 1;
|
|
|
|
|
|
|
|
|
|
// 使用flatMap的代码
|
|
|
|
|
String cityUsingFlatMap = getUserById(userId)
|
|
|
|
|
.flatMap(OptionalExample::getAddressByUser)
|
|
|
|
|
.map(Address::getCity)
|
|
|
|
|
.orElse("Unknown");
|
|
|
|
|
|
|
|
|
|
System.out.println("User's city using flatMap: " + cityUsingFlatMap);
|
|
|
|
|
|
|
|
|
|
// 不使用flatMap的代码
|
|
|
|
|
Optional<Optional<Address>> optionalAddress = getUserById(userId)
|
|
|
|
|
.map(OptionalExample::getAddressByUser);
|
|
|
|
|
|
|
|
|
|
String cityWithoutFlatMap;
|
|
|
|
|
if (optionalAddress.isPresent()) {
|
|
|
|
|
Optional<Address> addressOptional = optionalAddress.get();
|
|
|
|
|
if (addressOptional.isPresent()) {
|
|
|
|
|
Address address = addressOptional.get();
|
|
|
|
|
cityWithoutFlatMap = address.getCity();
|
|
|
|
|
} else {
|
|
|
|
|
cityWithoutFlatMap = "Unknown";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
cityWithoutFlatMap = "Unknown";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("User's city without flatMap: " + cityWithoutFlatMap);
|
|
|
|
|
}
|
|
|
|
|
```
|
2023-08-05 15:55:16 +08:00
|
|
|
|
|
2023-08-05 15:38:27 +08:00
|
|
|
|
在`Stream`和`Optional`中正确使用`flatMap`可以减少很多不必要的代码。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### 判断 value 是否为 null
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
/**
|
|
|
|
|
* value是否为null
|
|
|
|
|
*/
|
|
|
|
|
public boolean isPresent() {
|
|
|
|
|
return value != null;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 如果value不为null执行consumer.accept
|
|
|
|
|
*/
|
|
|
|
|
public void ifPresent(Consumer<? super T> consumer) {
|
|
|
|
|
if (value != null)
|
|
|
|
|
consumer.accept(value);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 获取 value
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
/**
|
|
|
|
|
* Return the value if present, otherwise invoke {@code other} and return
|
|
|
|
|
* the result of that invocation.
|
|
|
|
|
* 如果value != null 返回value,否则返回other的执行结果
|
|
|
|
|
*/
|
|
|
|
|
public T orElseGet(Supplier<? extends T> other) {
|
|
|
|
|
return value != null ? value : other.get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果value != null 返回value,否则返回T
|
|
|
|
|
*/
|
|
|
|
|
public T orElse(T other) {
|
|
|
|
|
return value != null ? value : other;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 如果value != null 返回value,否则抛出参数返回的异常
|
|
|
|
|
*/
|
|
|
|
|
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
|
|
|
|
|
if (value != null) {
|
|
|
|
|
return value;
|
|
|
|
|
} else {
|
|
|
|
|
throw exceptionSupplier.get();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* value为null抛出NoSuchElementException,不为空返回value。
|
|
|
|
|
*/
|
|
|
|
|
public T get() {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
throw new NoSuchElementException("No value present");
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 过滤值
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
/**
|
|
|
|
|
* 1. 如果是empty返回empty
|
|
|
|
|
* 2. predicate.test(value)==true 返回this,否则返回empty
|
|
|
|
|
*/
|
|
|
|
|
public Optional<T> filter(Predicate<? super T> predicate) {
|
|
|
|
|
Objects.requireNonNull(predicate);
|
|
|
|
|
if (!isPresent())
|
|
|
|
|
return this;
|
|
|
|
|
else
|
|
|
|
|
return predicate.test(value) ? this : empty();
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 小结
|
|
|
|
|
|
2023-08-05 15:55:16 +08:00
|
|
|
|
看完 `Optional` 源码,`Optional` 的方法真的非常简单,值得注意的是如果坚决不想看见 `NPE`,就不要用 `of()`、 `get()`、`flatMap(..)`。最后再综合用一下 `Optional` 的高频方法。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Date-Time API
|
|
|
|
|
|
|
|
|
|
这是对`java.util.Date`强有力的补充,解决了 Date 类的大部分痛点:
|
|
|
|
|
|
|
|
|
|
1. 非线程安全
|
|
|
|
|
2. 时区处理麻烦
|
|
|
|
|
3. 各种格式化、和时间计算繁琐
|
|
|
|
|
4. 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。
|
|
|
|
|
|
|
|
|
|
我们从常用的时间实例来对比 java.util.Date 和新 Date 有什么区别。用`java.util.Date`的代码该改改了。
|
|
|
|
|
|
|
|
|
|
### java.time 主要类
|
|
|
|
|
|
2023-04-28 17:31:44 +08:00
|
|
|
|
`java.util.Date` 既包含日期又包含时间,而 `java.time` 把它们进行了分离
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
|
|
|
|
|
LocalDate.class //日期 format: yyyy-MM-dd
|
|
|
|
|
LocalTime.class //时间 format: HH:mm:ss
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 格式化
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之前:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void oldFormat(){
|
2022-10-08 22:13:58 +08:00
|
|
|
|
Date now = new Date();
|
|
|
|
|
//format yyyy-MM-dd
|
2021-01-28 13:06:20 +08:00
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
String date = sdf.format(now);
|
|
|
|
|
System.out.println(String.format("date format : %s", date));
|
|
|
|
|
|
|
|
|
|
//format HH:mm:ss
|
|
|
|
|
SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss");
|
|
|
|
|
String time = sdft.format(now);
|
|
|
|
|
System.out.println(String.format("time format : %s", time));
|
|
|
|
|
|
|
|
|
|
//format yyyy-MM-dd HH:mm:ss
|
|
|
|
|
SimpleDateFormat sdfdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
String datetime = sdfdt.format(now);
|
|
|
|
|
System.out.println(String.format("dateTime format : %s", datetime));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之后:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void newFormat(){
|
|
|
|
|
//format yyyy-MM-dd
|
|
|
|
|
LocalDate date = LocalDate.now();
|
|
|
|
|
System.out.println(String.format("date format : %s", date));
|
|
|
|
|
|
|
|
|
|
//format HH:mm:ss
|
|
|
|
|
LocalTime time = LocalTime.now().withNano(0);
|
|
|
|
|
System.out.println(String.format("time format : %s", time));
|
|
|
|
|
|
|
|
|
|
//format yyyy-MM-dd HH:mm:ss
|
|
|
|
|
LocalDateTime dateTime = LocalDateTime.now();
|
|
|
|
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
2021-06-06 23:14:51 +08:00
|
|
|
|
String dateTimeStr = dateTime.format(dateTimeFormatter);
|
2021-01-28 13:06:20 +08:00
|
|
|
|
System.out.println(String.format("dateTime format : %s", dateTimeStr));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 字符串转日期格式
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之前:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
//已弃用
|
|
|
|
|
Date date = new Date("2021-01-26");
|
|
|
|
|
//替换为
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
Date date1 = sdf.parse("2021-01-26");
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之后:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
LocalDate date = LocalDate.of(2021, 1, 26);
|
|
|
|
|
LocalDate.parse("2021-01-26");
|
|
|
|
|
|
|
|
|
|
LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
|
|
|
|
|
LocalDateTime.parse("2021-01-26 12:12:22");
|
|
|
|
|
|
|
|
|
|
LocalTime time = LocalTime.of(12, 12, 22);
|
|
|
|
|
LocalTime.parse("12:12:22");
|
|
|
|
|
```
|
|
|
|
|
|
2023-04-28 17:31:44 +08:00
|
|
|
|
**Java 8 之前** 转换都需要借助 `SimpleDateFormat` 类,而**Java 8 之后**只需要 `LocalDate`、`LocalTime`、`LocalDateTime`的 `of` 或 `parse` 方法。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### 日期计算
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
下面仅以**一周后日期**为例,其他单位(年、月、日、1/2 日、时等等)大同小异。另外,这些单位都在 _java.time.temporal.ChronoUnit_ 枚举中定义。
|
|
|
|
|
|
|
|
|
|
**Java 8 之前:**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void afterDay(){
|
|
|
|
|
//一周后的日期
|
|
|
|
|
SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
Calendar ca = Calendar.getInstance();
|
|
|
|
|
ca.add(Calendar.DATE, 7);
|
|
|
|
|
Date d = ca.getTime();
|
|
|
|
|
String after = formatDate.format(d);
|
|
|
|
|
System.out.println("一周后日期:" + after);
|
|
|
|
|
|
|
|
|
|
//算两个日期间隔多少天,计算间隔多少年,多少月方法类似
|
|
|
|
|
String dates1 = "2021-12-23";
|
|
|
|
|
String dates2 = "2021-02-26";
|
|
|
|
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
Date date1 = format.parse(dates1);
|
|
|
|
|
Date date2 = format.parse(dates2);
|
|
|
|
|
int day = (int) ((date1.getTime() - date2.getTime()) / (1000 * 3600 * 24));
|
2022-09-24 15:03:45 +08:00
|
|
|
|
System.out.println(dates1 + "和" + dates2 + "相差" + day + "天");
|
|
|
|
|
//结果:2021-02-26和2021-12-23相差300天
|
2021-01-28 15:01:18 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Java 8 之后:**
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void pushWeek(){
|
|
|
|
|
//一周后的日期
|
|
|
|
|
LocalDate localDate = LocalDate.now();
|
|
|
|
|
//方法1
|
|
|
|
|
LocalDate after = localDate.plus(1, ChronoUnit.WEEKS);
|
|
|
|
|
//方法2
|
|
|
|
|
LocalDate after2 = localDate.plusWeeks(1);
|
|
|
|
|
System.out.println("一周后日期:" + after);
|
|
|
|
|
|
|
|
|
|
//算两个日期间隔多少天,计算间隔多少年,多少月
|
|
|
|
|
LocalDate date1 = LocalDate.parse("2021-02-26");
|
|
|
|
|
LocalDate date2 = LocalDate.parse("2021-12-23");
|
|
|
|
|
Period period = Period.between(date1, date2);
|
|
|
|
|
System.out.println("date1 到 date2 相隔:"
|
|
|
|
|
+ period.getYears() + "年"
|
|
|
|
|
+ period.getMonths() + "月"
|
|
|
|
|
+ period.getDays() + "天");
|
2023-12-30 17:14:13 +08:00
|
|
|
|
//打印结果是 “date1 到 date2 相隔:0年9月27天”
|
2021-01-28 15:01:18 +08:00
|
|
|
|
//这里period.getDays()得到的天是抛去年月以外的天数,并不是总天数
|
|
|
|
|
//如果要获取纯粹的总天数应该用下面的方法
|
|
|
|
|
long day = date2.toEpochDay() - date1.toEpochDay();
|
2022-09-24 15:03:45 +08:00
|
|
|
|
System.out.println(date1 + "和" + date2 + "相差" + day + "天");
|
|
|
|
|
//打印结果:2021-02-26和2021-12-23相差300天
|
2021-01-28 15:01:18 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### 获取指定日期
|
|
|
|
|
|
|
|
|
|
除了日期计算繁琐,获取特定一个日期也很麻烦,比如获取本月最后一天,第一天。
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之前:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void getDay() {
|
|
|
|
|
|
|
|
|
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
//获取当前月第一天:
|
|
|
|
|
Calendar c = Calendar.getInstance();
|
|
|
|
|
c.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
|
|
String first = format.format(c.getTime());
|
|
|
|
|
System.out.println("first day:" + first);
|
|
|
|
|
|
|
|
|
|
//获取当前月最后一天
|
|
|
|
|
Calendar ca = Calendar.getInstance();
|
|
|
|
|
ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
|
|
|
|
|
String last = format.format(ca.getTime());
|
|
|
|
|
System.out.println("last day:" + last);
|
|
|
|
|
|
|
|
|
|
//当年最后一天
|
|
|
|
|
Calendar currCal = Calendar.getInstance();
|
|
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
|
|
calendar.clear();
|
|
|
|
|
calendar.set(Calendar.YEAR, currCal.get(Calendar.YEAR));
|
|
|
|
|
calendar.roll(Calendar.DAY_OF_YEAR, -1);
|
|
|
|
|
Date time = calendar.getTime();
|
|
|
|
|
System.out.println("last day:" + format.format(time));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
**Java 8 之后:**
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public void getDayNew() {
|
|
|
|
|
LocalDate today = LocalDate.now();
|
|
|
|
|
//获取当前月第一天:
|
|
|
|
|
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
|
|
|
|
|
// 取本月最后一天
|
|
|
|
|
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
|
|
|
|
|
//取下一天:
|
|
|
|
|
LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
|
|
|
|
|
//当年最后一天
|
|
|
|
|
LocalDate lastday = today.with(TemporalAdjusters.lastDayOfYear());
|
|
|
|
|
//2021年最后一个周日,如果用Calendar是不得烦死。
|
2021-06-06 23:31:23 +08:00
|
|
|
|
LocalDate lastMondayOf2021 = LocalDate.parse("2021-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
|
2021-01-28 13:06:20 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
`java.time.temporal.TemporalAdjusters` 里面还有很多便捷的算法,这里就不带大家看 Api 了,都很简单,看了秒懂。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### JDBC 和 java8
|
|
|
|
|
|
|
|
|
|
现在 jdbc 时间类型和 java8 时间类型对应关系是
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
1. `Date` ---> `LocalDate`
|
|
|
|
|
2. `Time` ---> `LocalTime`
|
2021-08-25 17:32:35 +08:00
|
|
|
|
3. `Timestamp` ---> `LocalDateTime`
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
而之前统统对应 `Date`,也只有 `Date`。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
### 时区
|
|
|
|
|
|
|
|
|
|
> 时区:正式的时区划分为每隔经度 15° 划分一个时区,全球共 24 个时区,每个时区相差 1 小时。但为了行政上的方便,常将 1 个国家或 1 个省份划在一起,比如我国幅员宽广,大概横跨 5 个时区,实际上只用东八时区的标准时即北京时间为准。
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
`java.util.Date` 对象实质上存的是 1970 年 1 月 1 日 0 点( GMT)至 Date 对象所表示时刻所经过的毫秒数。也就是说不管在哪个时区 new Date,它记录的毫秒数都一样,和时区无关。但在使用上应该把它转换成当地时间,这就涉及到了时间的国际化。`java.util.Date` 本身并不支持国际化,需要借助 `TimeZone`。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
//北京时间:Wed Jan 27 14:05:29 CST 2021
|
|
|
|
|
Date date = new Date();
|
|
|
|
|
|
|
|
|
|
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
//北京时区
|
|
|
|
|
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
|
|
|
|
System.out.println("毫秒数:" + date.getTime() + ", 北京时间:" + bjSdf.format(date));
|
|
|
|
|
|
|
|
|
|
//东京时区
|
|
|
|
|
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
|
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); // 设置东京时区
|
|
|
|
|
System.out.println("毫秒数:" + date.getTime() + ", 东京时间:" + tokyoSdf.format(date));
|
|
|
|
|
|
|
|
|
|
//如果直接print会自动转成当前时区的时间
|
|
|
|
|
System.out.println(date);
|
|
|
|
|
//Wed Jan 27 14:05:29 CST 2021
|
|
|
|
|
```
|
|
|
|
|
|
2023-08-05 15:55:16 +08:00
|
|
|
|
在新特性中引入了 `java.time.ZonedDateTime` 来表示带时区的时间。它可以看成是 `LocalDateTime + ZoneId`。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
//当前时区时间
|
|
|
|
|
ZonedDateTime zonedDateTime = ZonedDateTime.now();
|
|
|
|
|
System.out.println("当前时区时间: " + zonedDateTime);
|
|
|
|
|
|
|
|
|
|
//东京时间
|
|
|
|
|
ZoneId zoneId = ZoneId.of(ZoneId.SHORT_IDS.get("JST"));
|
|
|
|
|
ZonedDateTime tokyoTime = zonedDateTime.withZoneSameInstant(zoneId);
|
|
|
|
|
System.out.println("东京时间: " + tokyoTime);
|
|
|
|
|
|
|
|
|
|
// ZonedDateTime 转 LocalDateTime
|
|
|
|
|
LocalDateTime localDateTime = tokyoTime.toLocalDateTime();
|
|
|
|
|
System.out.println("东京时间转当地时间: " + localDateTime);
|
|
|
|
|
|
|
|
|
|
//LocalDateTime 转 ZonedDateTime
|
|
|
|
|
ZonedDateTime localZoned = localDateTime.atZone(ZoneId.systemDefault());
|
|
|
|
|
System.out.println("本地时区时间: " + localZoned);
|
|
|
|
|
|
|
|
|
|
//打印结果
|
|
|
|
|
当前时区时间: 2021-01-27T14:43:58.735+08:00[Asia/Shanghai]
|
|
|
|
|
东京时间: 2021-01-27T15:43:58.735+09:00[Asia/Tokyo]
|
|
|
|
|
东京时间转当地时间: 2021-01-27T15:43:58.735
|
|
|
|
|
当地时区时间: 2021-01-27T15:53:35.618+08:00[Asia/Shanghai]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 小结
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
通过上面比较新老 `Date` 的不同,当然只列出部分功能上的区别,更多功能还得自己去挖掘。总之 date-time-api 给日期操作带来了福利。在日常工作中遇到 date 类型的操作,第一考虑的是 date-time-api,实在解决不了再考虑老的 Date。
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
## 总结
|
|
|
|
|
|
2021-01-28 15:01:18 +08:00
|
|
|
|
我们梳理总结的 java 8 新特性有
|
2021-01-28 13:06:20 +08:00
|
|
|
|
|
|
|
|
|
- Interface & functional Interface
|
|
|
|
|
- Lambda
|
|
|
|
|
- Stream
|
|
|
|
|
- Optional
|
|
|
|
|
- Date time-api
|
|
|
|
|
|
2021-08-25 17:32:35 +08:00
|
|
|
|
这些都是开发当中比较常用的特性。梳理下来发现它们真香,而我却没有更早的应用。总觉得学习 java 8 新特性比较麻烦,一直使用老的实现方式。其实这些新特性几天就可以掌握,一但掌握,效率会有很大的提高。其实我们涨工资也是涨的学习的钱,不学习终究会被淘汰,35 岁危机会提前来临。
|
2023-08-07 18:56:33 +08:00
|
|
|
|
|
2023-10-27 06:44:02 +08:00
|
|
|
|
<!-- @include: @article-footer.snippet.md -->
|