标题
- Optional类
- 常用Api使用说明
- Optional API 使用建议
- Lambda表达式
- Lambda 表达式的局限性与解决方案
- Stream
- 案例实操
- 优化 Stream 操作
- 新的日期和时间API
- LocalDateTime在SpringBoot中的应用
- 函数式接口(Functional)
Optional博客参考地址1
Stream博客参考地址1
新的日期和时间API博客参考地址1
Optional类
Optional 类,是Java8 新增加的一个对象容器。主要的功能有:对象的创建、获取、判断、过滤,映射等。
① Optional是在java.util包下的一个用于代替null的一个工具类;
② Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
③ Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。Optional 类的引入很好的解决空指针异常。
功能 | 方法 |
---|---|
创建 | empty 、of 、ofNullable |
判断、检查 | isPresent 、ifPresent 、orElse 、orElseGet 、orElseThrow |
判断其他版本 | isEmpty(JDK11才有)、ifPresentOrElse(JDK9) |
获取 | get |
过滤 | filter |
映射 | map 、flatMap |
在对 Optional 对象进行操作时,必须先判断 Optional 对象中是否实际包含值,否则可能会抛出 NoSuchElementException 异常。使用isPresent()方法可以判断 Optional 对象中是否包含值,如果返回 true,则说明 Optional 对象中包含值,否则说明 Optional 对象为空。
常用Api使用说明
创建
Student student = new Student("王五", 80);
Optional<Student> optional = Optional.of(student);
Optional<Student> optional = Optional.ofNullable(student);
Optional.ofNullable(tUserInfo.getUserName()).orElse("")
Optional.of(handlerName).orElse("")
Optional<String> emptyOptional = Optional.empty();
if (emptyOptional.isPresent()) {
String value = emptyOptional.get();
} else {
System.out.println("Optional对象为空");
}
//都是创建Optional对象,他两区别在于of不允许参数是null,而ofNullable则无限制。
final Optional<Object> s3 = Optional.ofNullable(null);//Optional.empty
final Optional<String> s4 = Optional.of(null);//java.lang.NullPointerException
/** 当需要使用 Optional 类型的变量,但还不知道它的值应该是什么时,
可以使用Optional.empty()方法来初始化该变量,以表示该变量当前还没有被赋值。
*/
判断
isPresent()
isPresent()方法用于判断 Optional 对象中是否存在非 null 值。如果 Optional 对象中存在非 null 值,该方法会返回 true;否则返回 false。
Student student = null;
Optional<Student> optional = Optional.ofNullable(student);
if (optional.isPresent()){
System.out.println("student不为空的操作");
}else {
System.out.println("student为空的操作");
}
在 JDK 11 中新增的isEmpty()
方法与isPresent()方法的作用相反
String str = "Hello World";
Optional<String> optionalStr = Optional.ofNullable(str);
if (optionalStr.isEmpty()) {
System.out.println("Optional对象为空");
} else {
String value = optionalStr.get();
System.out.println("Optional对象的值为:" + value);
}
String str = "Hello World";
Optional<String> optionalStr = Optional.ofNullable(str);
if (optionalStr.isPresent()) {
String value = optionalStr.get();
System.out.println("Optional对象的值为:" + value);
} else {
System.out.println("Optional对象为空");
}
ifPresent(Consumer<? super T> consumer)
方法
ifPresent()方法接受一个Consumer对象(消费函数),如果包装对象的值非空,运行Consumer对象的accept()方法
if(null != order){
order.setAmount(orderInfoVo.getAmount());
}
//等价于
Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));
Optional<String> optionalStr = Optional.of("Hello World!");
optionalStr.ifPresent(str -> System.out.println("Optional 中包含非空字符串:" + str));
java 9里新增了 ifPresentOrElse()
,当 Optional 里的值不为空则执行第一个参数里的代码,为空则执行第二个参数的代码,相当于 if-else
Student student = new Student("张三", 22);
Optional<Student> optional = Optional.ofNullable(student);
optional.ifPresentOrElse( value -> System.out.println("student不为空,姓名:"+value.getName()), () -> System.out.println("student为空") );
orElse
orElse(T other)方法用于在 Optional 对象不包含非 null 值的情况下提供默认值。即检查Optional实例是否有值,如果实例非null,就返回实例值,否则返回指定的其它值
需要注意的是,调用orElse(T other)方法时必须指定一个非 null 的值作为参数,用于在 Optional 对象中不存在实际值时返回。否则抛出 NullPointerException 异常
String str = null;
Optional<String> optionalStr = Optional.ofNullable(str);
String value = optionalStr.orElse("默认值");
System.out.println("Optional对象的值为:" + value);
orElseGet
orElseGet(Supplier<? extends T> other) 方法与 orElse(T other) 方法类似,都是在 Optional 对象不包含非 null 值的情况下提供默认值。不同的是,orElseGet(Supplier<? extends T> other) 方法需要传递一个 Supplier 对象作为参数,在 Optional 对象不包含非 null 值时,会调用该 Supplier 对象提供的方法来生成默认值。
需要注意的是,使用 orElseGet(Supplier<? extends T> other) 方法时,传递的 Supplier 对象不能为 null,否则会抛出 NullPointerException 异常。
String str = null;
Optional<String> optionalStr = Optional.ofNullable(str);
String value = optionalStr.orElseGet(() -> "默认值");
System.out.println("Optional对象的值为:" + value);
User user = new User();//注意这可不是空 User user = null; 才是
User user2 = new User("anna@gmail.com", "1234");
User result = Optional.ofNullable(user).orElse(user2);
User result = Optional.ofNullable(user).orElseGet( () -> user2);
orElseThrow
如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
orElseThrow()方法其实与orElseGet()方法非常相似了,入参都是Supplier对象,只不过orElseThrow()的Supplier对象必须返回一个Throwable异常,并在orElseThrow()中将异常抛出,orElseThrow()方法适用于包装对象值为空时需要抛出特定异常的场景。
如果在调用orElseThrow() 方法时传递的 Supplier 参数为 null,则会抛出NullPointerException 异常。因此,在调用 orElseThrow(Supplier<? extends X> throwableSupplier) 方法时必须指定一个非null的Supplier函数。
Optional.ofNullable(orderInfoVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s订单的amount不能为NULL",orderInfoVo.getOrderId())));
Optional<String > optional = Optional.ofNullable(null);
String bb = optional.orElseThrow(() -> new Exception("抛出异常"));
案例应用
// 校验文件大小
Optional<MultipartFile> optionalFile = files.stream().filter(file -> file.getSize() > Constants.MAX_FILE_SIZE).findAny();
if (optionalFile.isPresent()) {
log.error("文件上传失败,文件大小超过2M,文件大小为:{}M", optionalFile.get().getSize()/1048576);
}
Optional<AssuredInfoVo> assuredInfoVoOptional = insureInfoVo.getAssuredInfoVos().parallelStream().filter(assuredInfoVo -> StringUtils.isNotBlank(assuredInfoVo.getAssuredCertificateNo())&&assuredInfoVo.getAssuredCertificateNo().equals(prpTinsuredVo.getIdentifyNumber())).findAny();
Optional<RenewalClaimResult.registInfo> optional = renewalClaimResult.getCaseDataList().stream().filter(caseData -> OrderConstants.NOCAR_CLAIM_STATUS_BLACK.equals(caseData.getEndCaseData())).findAny();
if (assuredInfoVoOptional.isPresent()) {
applyNo = assuredInfoVoOptional.get().getApplyNo();
policyNo = assuredInfoVoOptional.get().getPolicyNo();
}
获取
使用 get 方法从 Optional 对象中获取值,注意一般结合isPresent使用,避免空指针异常
Student student = null;
Optional<Student> optional = Optional.ofNullable(student);
if (optional.isPresent()){
Student myStudent = optional.get();
System.out.println("姓名:"+myStudent.getName());
}else {
System.out.println("student为空");
}
过滤
filter(Predicate<? super T> predicate)
filter是 Optional 类中的一个方法,它用于过滤 Optional 对象中的值,只有当值满足特定条件时才保留。
该方法接受一个 Predicate 参数,这个参数是一个函数式接口,需要自己实现其中的 test(T t) 方法。test(T t) 方法用于测试指定对象是否符合特定条件。
如果 Optional 对象中的值满足 Predicate 参数中的条件,则返回该值所在的 Optional 对象,否则返回一个空的 Optional 对象。
Optional<String> optionalStr = Optional.of("Hello World!");
Optional<String> filtered = optionalStr.filter(str -> !str.contains("World"));
//过滤后的 Optional 对象:Optional.empty
System.out.println("过滤后的 Optional 对象:" + filtered);
映射
map(Function<? super T, ? extends U> mapper)
map是 Optional 类中的一个方法,用于对 Optional 对象中的值进行转换,并返回一个新的 Optional 对象。该方法接受一个 Function 参数,这个参数也是一个函数式接口,需要自己实现其中的 apply(T t) 方法。apply(T t) 方法用于将指定类型的对象转换为另一种类型的对象。
如果 Optional 对象中的值不为空,则将该值作为参数传递给 Function 对象中的 apply(T t) 方法,得到一个新的对象作为转换后的结果;否则直接返回一个空的 Optional 对象。
需要注意的是,在调用 map 方法时,返回值是一个新的 Optional 对象,因此我们可以在该对象上连续调用其他方法来进行操作,比如 orElse(T other)、orElseGet(Supplier<? extends T> other) 等。同时如果 Function 对象中的转换过程中抛出了异常,则该方法也会将该异常包装进一个 RuntimeException 中重新抛出。
Optional<String> optionalStr = Optional.of("Hello World!");
Optional<Integer> lengthOptional = optionalStr.map(String::length);
lengthOptional.ifPresent(len -> System.out.println("字符串长度为:" + len));
List<String> companyNames = Arrays.asList("paypal", "oracle", "", "microsoft", "", "apple");
Optional<List<String>> listOptional = Optional.of(companyNames);
//获取companyNames的长度
int size = listOptional.map(List::size).orElse(0);
System.out.println(size);//6
flatMap(Function<? super T, Optional<U>> mapper)
flatMap是 Optional 类中的一个方法,用于 对 Optional 对象中的值进行转换,并返回一个新的 Optional 对象。 该方法接受一个 Function 参数,这个参数也是一个函数式接口,需要自己实现其中的 apply(T t) 方法。apply(T t) 方法用于将指定类型的对象转换为另一种类型的 Optional 对象。
如果 Optional 对象中的值不为空,则将该值作为参数传递给 Function 对象中的 apply(T t) 方法,得到一个新的 Optional 对象作为转换后的结果;否则直接返回一个空的 Optional 对象。与 map 方法不同,flatMap 方法可以将最终产生的 Optional 对象展开,使其成为一个扁平的对象。
需要注意的是,在调用 flatMap 方法时,返回值是一个新的 Optional 对象,因此我们可以在该对象上连续调用其他方法来进行操作,比如 orElse(T other)、orElseGet(Supplier<? extends T> other) 等。同时如果 Function 对象中的转换过程中抛出了异常,则该方法也会将该异常包装进一个 RuntimeException 中重新抛出。
Optional<String> optionalStr = Optional.of("Hello World!");
Optional<Integer> lengthOptional = optionalStr.flatMap(str -> {
if (str.contains("World")) {
return Optional.of(str.length());
} else {
return Optional.empty();
}
});
lengthOptional.ifPresent(len -> System.out.println("字符串长度为:" + len));
组合使用 filter 和 map 方法
Optional<String> optionalStr = Optional.of("123");
int value = optionalStr
.filter(str -> str.length() > 0) // 筛选出非空字符串
.map(Integer::parseInt) // 将字符串转换成整型数字
.orElse(0); // 如果 Optional 对象为空,返回默认值 0
System.out.println(value); // 输出结果为 123
组合使用 filter 和 flatMap 方法
Optional<String> optionalStr = Optional.of("hello, world");
optionalStr.filter(str -> str.contains("world2")) // 筛选出包含 "world" 的字符串
.flatMap(str -> Optional.of(str.length())) // 将符合条件的字符串封装成新的 Optional 对象
.ifPresent(System.out::println); // 输出长度大于等于 5 的字符串
在调用 Optional 方法组合时,每一个方法的返回值都是一个 Optional 对象,因此可以在该对象上继续调用其他方法,直到最终得到需要的值。同时,如果在方法组合的过程中出现了空值,那么后续的方法将不会被执行,并返回一个空的 Optional 对象。
Optional<String> optionalStr = Optional.of("Hello, World!");
optionalStr
.filter(str -> str.contains("World")) // 筛选出包含 "World" 的字符串
.map(String::length) // 将剩余的字符串映射成其长度
.flatMap(len -> {
if (len >= 10) {
return Optional.of("String length: " + len);
} else {
return Optional.empty();
}
}) // 对字符串长度进行判断并封装成 Optional 对象
.ifPresent(System.out::println); // 输出长度大于等于 10 的字符串
SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal != null && principal instanceof LoginUser) {
return (LoginUser) principal;
}
}
// 使用Java 8中的Optional类优化上述代码,使其更加简洁和易读。优化后的代码如下:
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.map(Authentication::getPrincipal)
.filter(principal -> principal instanceof LoginUser)
.map(LoginUser.class::cast)
.orElse(null);
stream ()方法
在 Java 9中添加到 Optional 类的最后一个方法是 stream() 方法,允许我们将 Optional 实例视为Stream
Optional<List<String>> value = Optional.of(Arrays.asList("aaaa","bbbb","ccc"));
List<String> collect = value.stream()
.flatMap(Collection::stream)
.filter(v->v.length()==4)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(collect);//[AAAA, BBBB]
}
Optional API 使用建议
1、不要滥用 Optional API
String finalStatus = Optional.ofNullable(status).orElse("PENDING")
// 这种写法不仅降低了代码可读性还无谓得创建了一个Optional对象(浪费性能)
// 以下是同等功能但更简洁更可读的实现
String finalStatus = status == null ? "PENDING" : status;
2、不要使用Optional作为Java Bean实例域的类型,因为 Optional 没有实现 Serializable 接口(不可序列化)
3、不要使用 Optional 作为类构造器参数
4、不要使用 Optional 作为Java Bean Setter方法的参数
-
原因除了上面第二点提到的 Optional 是不可序列化的,还有降低了可读性。
-
既然 setter是用于给Java Bean 属性赋值的, 为什么还无法确定里面的值是不是空 ? 如果为空,为何不直接赋值 null (或者对应的空值) ?
-
但相反的是,对于可能是空值 Java Bean 属性的 Getter 方法返回值使用 Optional 类型是很好的实践。由于getter返回的是Optional,外部调用时就意识到里面可能是空结果,需要进行判断。注意:对值可能为 null 的实例域的 getter 才需要使用 Optional。
@Entity
public class Customer implements Serializable {
private String postcode; // optional field, thus may be null
public Optional<String> getPostcode() {
return Optional.ofNullable(postcode);
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
...
}
5、不要使用Optional作为方法参数的类型
首先,当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。其次,当方法有多个Optional参数时,方法签名会变得更长,可读性更差。
6、不要给Optional变量赋值 null,而应该用 Optional.empty() 表达空值
7、确保Optional内有值才能调用 get() 方法
8、尽量使用 Optional 提供的快捷API 避免手写 if-else 语句
9、使用 equals 而不是 == 来比较 Optional 的值,Optional 的 equals 方法已经实现了内部值比较
Lambda表达式
范式: 类名::方法名
person -> person.getAge(); =====> Person::getAge
x -> System.out.println(x) =====> System.out::println
//out是一个PrintStream类的对象,println是该类的方法,依据x的类型来重载方法
创建对象
() -> new ArrayList<>(); =====> ArrayList::new
new关键字实际上调用的是ArrayList的构造方法
Lambda 表达式的局限性与解决方案
Lambda 表达式无法直接访问非 final 的局部变量。这是因为 Lambda 表达式可能在一个新的线程中执行,而局部变量的生命周期可能早已结束。为了解决这个问题,Java 8 引入了 “Effectively Final” 的概念。如果一个局部变量在初始化后没有被修改过,那么它就被视为 “Effectively Final”,Lambda 表达式可以访问这样的变量。例如,以下代码是合法的,因为 text变量是 “Effectively Final”:
String text = "Hello, Lambda!";
Runnable runnable = () -> System.out.println(text);
然而,如果我们尝试修改 text 变量,编译器将报错:
String text = "Hello, Lambda!";
text = "Modified";
Runnable runnable = () -> System.out.println(text); // 编译错误
Lambda 表达式不允许直接抛出受检异常(checked exception)。为了解决这个问题,我们可以使用函数式接口封装受检异常。
以下是一个简单的示例:首先,我们创建一个自定义函数式接口 ThrowingFunction,它可以抛出受检异常:
@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E;
}
接下来,我们创建一个辅助方法 wrapFunction,它将 ThrowingFunction 转换为一个不抛出受检异常的 Function:
public static <T, R, E extends Exception> Function<T, R> wrapFunction(ThrowingFunction<T, R, E> throwingFunction) {
return t -> {
try {
return throwingFunction.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
现在,我们可以使用 wrapFunction 方法处理 Lambda 表达式中的受检异常。例如,以下代码使用 Files.readAllLines 方法读取文件的所有行,该方法可能抛出 IOException:
List<String> filePaths = Arrays.asList("file1.txt", "file2.txt");
Function<String, List<String>> readAllLines = wrapFunction(Files::readAllLines);
List<List<String>> lines = filePaths.stream()
.map(readAllLines)
.collect(Collectors.toList());
通过这种方式,我们可以在 Lambda 表达式中处理受检异常,而无需破坏函数式编程的风格。
方法引用的高级用法
方法引用是 Lambda 表达式的一种简化形式,它允许我们直接引用现有的方法,而无需显式地编写 Lambda 表达式。
方法引用有四种形式:静态方法引用、实例方法引用、构造方法引用和类实例方法引用。
我们将详细介绍这四种方法引用的用法,并通过实例演示它们在实际开发中的应用。
2.3.1. 静态方法引用
静态方法引用的语法是 ClassName::staticMethodName。它允许我们引用类的静态方法。
例如,我们可以使用 Integer::parseInt 作为 Function<String, Integer> 的实例:
Function<String, Integer> parseInt = Integer::parseInt;
Integer result = parseInt.apply("123");
System.out.println(result); // 输出:123
2.3.2. 实例方法引用
实例方法引用的语法是 instance::instanceMethodName。它允许我们引用对象的实例方法。
例如,我们可以使用 "Hello, world!"::toUpperCase 作为 Supplier<String> 的实例:
Supplier<String> toUpperCase = "Hello, world!"::toUpperCase;
String upperCaseResult = toUpperCase.get();
System.out.println(upperCaseResult); // 输出:HELLO, WORLD!
2.3.3. 构造方法引用
构造方法引用的语法是 ClassName::new。它允许我们引用类的构造方法。例如,我们可以使用
ArrayList::new 作为 Supplier<List<String>> 的实例:
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
list.add("Hello, world!");
System.out.println(list); // 输出:[Hello, world!]
2.3.4. 类实例方法引用
Comparator<String> comparator = String::compareToIgnoreCase;
int comparisonResult = comparator.compare("hello", "HELLO");
System.out.println(comparisonResult); // 输出:0
通过这些示例,我们可以看到方法引用在实际开发中的应用。方法引用可以简化 Lambda 表达式,提高代码的可读性和可维护性。在实际开发中,我们可以根据需要选择合适的方法引用形式,以实现更简洁、优雅的编程方式。
Stream
并行 Stream 不总是比顺序 Stream 更快。在数据量较小或处理器核心数较少的情况下,顺序 Stream 可能更有优势。因此,在使用并行 Stream 时,建议根据实际情况进行测试和调整。
Stream的特性:
- stream不存储数据
- stream不改变源数据
- stream的延迟执行特性
特性说明:
-
【使用步骤】使用一个Stream流,一般分为三个步骤:
1.获取数据源-> 2. 中间操作(Intermediate)-> 3. 终端操作(Terminal)。 -
【操作分类】操作分为中间操作与终端操作
一个中间操作链,对数据源的数据进行处理,比如fifter、distinct、limit、skip、map、sorted。在一个流上执行终结操作之后,该流被消费,无法再次被消费,比如:match、findFirst、count、max、min、forEach、reduce、collect。
创建流
-
数组
1. Arrays.stream(T array);
2. stream.of(array) -
Collection
1. Collection.stream()
2. Collection.parallelStream() -
其他常用流
1. 空流:Stream.empty()
2. 无限数据流:Stream.generate()
3. 规律的无限数流:Stream.iterate()
//1.通过Arrays.stream
//1.1基本类型
int[] arr = new int[]{1, 2, 34, 5};
IntStream intStream = Arrays.stream(arr);
//1.2引用类型
Student[] studentArr = new Student[]{new Student("s1", 29), new Student("s2", 27)};
Stream<Student> studentStream = Arrays.stream(studentArr);
//2.通过Stream.of
Stream<Integer> stream1 = Stream.of(1, 2, 34, 5);
//注意生成的是int[]的流
Stream<int[]> stream2 = Stream.of(arr, arr);
stream2.forEach(System.out::println);
//3.通过数组创建
List<String> strs = Arrays.asList("11212", "dfd", "2323", "dfhgf");
//3.1创建普通流
Stream<String> stream3 = strs.stream();
//3.2创建并行流
Stream<String> stream4 = strs.parallelStream();
//4.创建一个空的stream
Stream<Integer> stream5 = Stream.empty();
//5.创建无限流,通过limit提取指定大小
Stream.generate(() -> "number" + new Random().nextInt()).limit(100).forEach(System.out::println);
Stream.generate(() -> new Student("name", 10)).limit(20).forEach(System.out::println);
//6.产生规律的数据
Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::println);
Stream.iterate(0, x -> x).limit(10).forEach(System.out::println);
//Stream.iterate(0,x->x).limit(10).forEach(System.out::println);与如下代码意思是一样的
Stream.iterate(0, UnaryOperator.identity()).limit(10).forEach(System.out::println);
操作流
Stream API 提供了丰富的操作,可以分为两类:中间操作(Intermediate Operations)和终止操作(Terminal Operations)。 中间操作是对 Stream 进行转换的操作,它们返回一个新的 Stream。以下是一些常见的中间操作:
- filter(Predicate):过滤 Stream 中满足条件的元素
- map(Function<T, R>):将 Stream中的元素转换为另一种类型
- flatMap(Function<T, Stream>):将 Stream 中的元素转换为其他Stream,然后将这些 Stream 合并为一个 Stream
- distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除Stream中重复元素
- limit(long maxSize) 截断流,使其元素不超过给定数量
- skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回空
- sorted():对 Stream 中的元素进行排序,可以传入自定义的 Comparator终止操作是
对 Stream进行最终处理的操作,它们返回一个结果或产生一个副作用。
以下是一些常见的终止操作:
- forEach(Consumer):对 Stream 中的每个元素执行操作 toArray():将 Stream 转换为数组
- reduce(BinaryOperator):将 Stream 中的元素进行归约操作,如求和、求积等
- collect(Collector<T, A, R>):将 Stream 转换为其他数据结构,如 List、Set、Map 等
- min(Comparator) 和 max(Comparator):求 Stream 中的最小值和最大值
- count():计算Stream 中的元素个数
- anyMatch(Predicate)、 allMatch(Predicate)和 noneMatch(Predicate):
筛选(过滤)与切片:filter()、distinct()、limit() skip()
@Test//1.筛选(过滤)与切片:filter()、distinct()、limit() skip()
public void test1() {
//1.接收 Lambda , 从流中排除某些元素
List<User> data = UserList.getData();
//1.判断工资大于9000的用户
data.stream().filter(user->user.getMoney()>9000).forEach(System.out::println);
//2.截取前三条数据
data.stream().limit(3).forEach(System.out::println);
//3.跳过前三条记录
data.stream().skip(3).forEach(System.out::println);
//4.去重复数据 // 要重写hashCode 和equals方法
data.stream().distinct().forEach(System.out::println);
}
映射:map、flatMap
- map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。
- mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。
- mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
- mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 ` 素上,产生一个新的 LongStream。
- flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test//2.映射
public void test2() {
//1.映射 将小写字母映射成大小字母
List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream().map(t->t.toUpperCase()).forEach(System.out::println);
list.stream().map(String::toUpperCase).forEach(System.out::println);
//2.映射用户姓名等于1的姓名
List<User> data = UserList.getData();
Stream<User> filter = data.stream().filter(user->user.getName().length()==1);
filter.map(User::getName).forEach(System.out::println);
System.out.println("-----------");
//3.flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list2 = Arrays.asList("aaa","bbb","ccc","ddd");
list2.stream().flatMap(str->StreamDemo1.getStream(str)).forEach(System.out::println);
}
public static Stream<Character> getStream(String str) {
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
排序:sorted
- sorted() 产生一个新流,其中按自然顺序排序
- sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
@Test//3.排序
public void test3() {
//1.自然排序
List<Integer> list = Arrays.asList(1,2,3,9,-1,0);
list.stream().sorted().forEach(System.out::println);
//2.定制排序
List<User> listUser = UserList.getData();
listUser.stream().sorted((u1,u2)->{
return Double.compare(u1.getMoney(), u2.getMoney());
}).forEach(System.out::println);
}
匹配与查找
@Test
public void test1() {
//1.查看所有有用户的钱都大于5000
List<User> list = UserList.getData();
boolean tag = list.stream().allMatch(u->u.getMoney()>5000);
System.out.println(tag);
//2.至少一个一个用户的前大于9000的
System.out.println(list.stream().anyMatch(u->u.getMoney()>9000));
//3.查看是否没有叫蔡文姬的用户
System.out.println(list.stream().noneMatch(u->u.getName().equals("蔡文姬")));
//4.返回第一个元素
Optional<User> findFirst = list.stream().findFirst();
System.out.println(findFirst.get());
}
@Test
public void test2() {
List<User> list = UserList.getData();
//1.返回流中总个数
long count = list.stream().count();
System.out.println(count);
//2.返回流中最大值
List<Double> list2 = Arrays.asList(12.2,3.3,99.9);
Optional<Double> max = list2.stream().max((o1,o2)->Double.compare(o1, o2));
System.out.println(max.get());
//3.内部迭代
list2.stream().forEach(System.out::println);
//4.外部集合直接迭代
list2.forEach(System.out::println);
}
收集
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
@Test//收集
public void test4() {
Set<User> set = UserList.getData().stream().filter(u->u.getMoney()>9000).collect(Collectors.toSet());
set.forEach(System.out::println);
}
案例实操
//任务是找出年龄在 18 岁以上,成绩在 80 分以上的学生,并按成绩降序排列
List<Student> qualifiedStudents = students.stream()
.filter(student -> student.getAge() > 18)
.filter(student -> student.getScore() > 80)
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.collect(Collectors.toList());
//筛选出年龄在 18 到 25 岁之间的用户;按照年龄升序排列;
//将筛选和排序后的用户姓名转换为大写形式;将最终结果存储到一个新的列表中。
List<String> result = users.stream()
.filter(user -> user.getAge() >= 18 && user.getAge() <= 25)
.sorted(Comparator.comparingInt(User::getAge))
.map(user -> user.getName().toUpperCase())
.collect(Collectors.toList());
//根据用户的年龄进行筛选、排序,并将用户名转换为大写字母
List<String> sortedUsernames = users.stream()
.filter(user -> user.getAge() >= 18)
.map(User::getUsername)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
优化 Stream 操作
为了提高 Stream 处理的性能,我们可以对 Stream 操作进行优化。以下是一些常见的优化技巧:
- 尽量使用基本类型的 Stream(如 IntStream、 LongStream、 DoubleStream),以减少装箱和拆箱的开销。
- 在适当的情况下,使用 flatMap 替换多个 map 操作,以减少中间结果的生成。
- 使用 filter 和 map 的顺序对性能有影响。尽量先执行 filter 操作,以减少后续操作的数据量。
- 对于有状态的操作(如 distinct、 sorted),尽量将它们放在 Stream 操作的末尾,以减少中间状态的维护开销。
新的日期和时间API
-
LocalDate:表示日期(年、月、日)
-
LocalTime:表示时间(时、分、秒、纳秒)
-
LocalDateTime:表示日期和时间
-
ZonedDateTime:表示带时区的日期和时间
-
Duration:表示时间段(以秒和纳秒为单位)
-
Period:表示日期段(以年、月、日为单位)
-
Instant:表示时间点(以Unix时间戳为基础)
LocalDate:表示日期,包含:年月日。格式为:2020-01-13 LocalTime:表示时间,包含:时分秒。格式为:16:39:09.307 LocalDateTime:表示日期时间,包含:年月日 时分秒。格式为:2020-01-13T16:40:59.138 DateTimeFormatter:日期时间格式化类 Instant:时间戳类 Duration:用于计算 2 个时间(LocalTime,时分秒)之间的差距 Period:用于计算 2 个日期(LocalDate,年月日)之间的差距 ZonedDateTime:包含时区的时间
常规操作日期调用方法
LocalDate today = LocalDate.now();//2024-07-02
//日期操作
LocalDate tomorrow = today.plusDays(1);//2024-07-03
LocalDate nextMonth = today.plusMonths(1);//2024-08-02
LocalDate lastYear = today.minusYears(1);//2023-07-02
LocalDate nextYear = today.plusYears(1);//2025-07-02
//日期比较
LocalDate date1 = LocalDate.of(2023, 6, 14);
LocalDate date2 = LocalDate.of(2024, 6, 14);
boolean isBefore = date1.isBefore(date2);//true
boolean isAfter = date1.isAfter(date2);//false
//计算两个日期之间的天数
LocalDate startDate = LocalDate.of(2023, 6, 14);
LocalDate endDate = LocalDate.of(2024, 6, 14);
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
System.out.println(daysBetween); // 输出: 365
//格式化日期为特定格式的字符串
LocalDate date = LocalDate.of(2024, 6, 14);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy");
String formattedDate = date.format(formatter);
System.out.println(formattedDate); // 输出: 14 Jun 2024
LocalDate localDate1 = LocalDate.now();
// 1、添加15天
LocalDate localDate2 = localDate1.plus(15, ChronoUnit.DAYS);
// 2、添加15天
LocalDate localDate3 = localDate1.plus(Period.ofDays(15));
// 3、增加15天
LocalDate localDate4 = localDate1.plusDays(15);
// 4、增加15周
LocalDate localDate5 = localDate1.plusWeeks(15);
// 5、增加15月
LocalDate localDate6 = localDate1.plusMonths(15);
// 6、增加15年
LocalDate localDate7 = localDate1.plusYears(15);
// 获取当前日期
LocalDate localDate1 = LocalDate.now();
System.out.println(localDate1);//2024-07-02
// 2、设置为周四
LocalDate localDate2 = localDate1.with(DayOfWeek.THURSDAY);
System.out.println(localDate2);//2024-07-04
// 3、设置到2050年
LocalDate localDate3 = localDate1.with(ChronoField.YEAR, 2050);
System.out.println(localDate3);//2050-07-02
// 4、设置到2050年
LocalDate localDate7 = localDate1.withYear(2050);
System.out.println(localDate7);//2050-07-02
// 5、设置到10号
LocalDate localDate4 = localDate1.withDayOfMonth(10);
System.out.println(localDate4);//2024-07-10
// 6、设置到一年的第364天:有效值为 1 到 365,闰年的有效值为 1 到 366
LocalDate localDate5 = localDate1.withDayOfYear(364);
System.out.println(localDate5);//2024-12-29
// 7、设置到10月份: 有效值是1到12
LocalDate localDate6 = localDate1.withMonth(10);
System.out.println(localDate6);//2024-10-02
比较日期
LocalDate localDate1 = LocalDate.parse("2021-02-14");
LocalDate localDate2 = LocalDate.parse("2022-05-20");
LocalDate localDate3 = LocalDate.parse("2021-02-14");
System.out.println(localDate1.isAfter(localDate2));//false
System.out.println(localDate1.isBefore(localDate2));//true
System.out.println(localDate1.isEqual(localDate3));//true
System.out.println("================判断==============");
System.out.println(localDate1.isLeapYear());//false
System.out.println(localDate1.isSupported(ChronoField.DAY_OF_MONTH));//true
System.out.println(localDate1.isSupported(ChronoUnit.HOURS));//false,不支持小时字段
System.out.println("=================比较==========================");
System.out.println(localDate1.equals(localDate2));//false
// 1:大于,0:等于,-1:小于
System.out.println(localDate1.compareTo(localDate2));//-1
System.out.println("=================最大值==========================");
System.out.println(localDate1.lengthOfMonth());//28天
System.out.println(localDate1.lengthOfYear());//365天
LocalDate
// 通过 of方法指定年月日
// 注意:年取值范围是,-999999999到999999999,彰显格局,月:1-12,日期:1-31
LocalDate date = LocalDate.of(2022, 12, 31);
// 获取年:2022
int year = date.getYear();
// 获取月份,返回Month对象,因为还可以基于月份做很多其他的判断,所以返回的并不是一个数字
// 直接打印month的话输出的是:DECEMBER,12月,也就是当前对象的英文表示
Month month = date.getMonth();
// 调用getValue返回的是数字月份:12
int monthValue = month.getValue();
// 获取是几号:31
int dayOfMonth = date.getDayOfMonth();
// 获取是周几,返回值与月份同理:SATURDAY
DayOfWeek dayOfWeek = date.getDayOfWeek();
// 调用getValue返回数字:6,因为31号是周六
int dayOfWeekValue = dayOfWeek.getValue();
// 获取是年中的第几天:365
int dayOfYear = date.getDayOfYear();
// 判读是否为闰年:false
boolean leapYear = date.isLeapYear();
// 获取当前日期
LocalDate date = LocalDate.now();
// 获取年
int year = date.get(ChronoField.YEAR);
// 获取 月份
int month = date.get(ChronoField.MONTH_OF_YEAR);
// 获取 几号
int dayOfMonth = date.get(ChronoField.DAY_OF_MONTH);
// 获取 今天是今年第几天
int dayOfYear = date.get(ChronoField.DAY_OF_YEAR);
// 获取 星期几
int dayOfWeek = date.get(ChronoField.DAY_OF_WEEK);
LocalTime
取值范围合理:
小时:0-23
分钟:0-59
秒钟:0-59
纳秒:0-999999999
// 设置指定时间
LocalTime time = LocalTime.of(13, 14, 52);
// 时钟:13
int hour = time.getHour();
// 分钟:14
int minute = time.getMinute();
// 秒钟:52
int second = time.getSecond();
// 纳秒:0
int nano = time.getNano();
// 也通过get根据 ChronoField枚举值获取数据
int hourOfDay = time.get(ChronoField.HOUR_OF_DAY);
// 通过 now() 获取当前时间
LocalTime now = LocalTime.now();
/**字符串创建:
可以调用LocalDate 和LocalTime 的parse方法根据字符串解析为日期和时间对象*/
LocalDate localDate = LocalDate.parse("2022-12-31");
LocalTime localTime = LocalTime.parse("13:14:52");
LocalDateTime
是对LocalDate和LocalTime的合并,同时表示了日期和时间,但不带有时区信息,可以直接创建,也可以通过合并日期和时间对象构造。
LocalDateTime,顾名思义,表示没有时区的日期和时间。它提供了丰富的API来操作日期和时间,如获取年、月、日、时、分、秒等。更重要的是,LocalDateTime是不可变且线程安全的,这意味着一旦创建了LocalDateTime对象,就不能修改它,而且多个线程可以安全地共享同一个对象。
// 创建日期
LocalDate date = LocalDate.now();
// 创建时间
LocalTime time = LocalTime.now();
// 1、合并日期和时间
LocalDateTime dateTime1 = LocalDateTime.of(date, time);
//2、通过atTime方法
LocalDateTime dateTime4 = date.atTime(time);
//3、通过atDate方法
LocalDateTime dateTime5 = time.atDate(date);
//4、通过年月日时分秒创建
LocalDateTime dateTime2 = LocalDateTime.of(2022, 12, 31, 13, 14, 52);
//5、创建当前日期和时间
LocalDateTime dateTime3 = LocalDateTime.now();
// 获取日期对象
LocalDate localDate = dateTime3 .toLocalDate();
// 获取时间对象
LocalTime localTime = dateTime3 .toLocalTime();
//获取当前日期和时间 该方法返回一个表示当前日期和时间的LocalDateTime对象。
LocalDateTime localDateTime = LocalDateTime.now();//2024-07-02T15:33:11.609
//创建特定日期时间对象,可以使用of()方法创建特定的日期时间对象。该方法接受年、月、日、时、分、秒等参数,并返回一个表示指定日期时间的LocalDateTime对象。
LocalDateTime localDateTime2 = LocalDateTime.of(2024, 7, 2, 5, 30, 45);//2024-07-02T05:30:45
/** 有时,我们可能需要将毫秒数转换为LocalDateTime对象。这可以通过Instant类和ZoneId类来实现。
首先,使用Instant类的静态方法ofEpochMilli()将毫秒数转换为Instant对象。然后,使用ZoneId类获取时区,并将Instant对象转换为LocalDateTime对象。*/
long millis = System.currentTimeMillis();
//long millis = 1675160265000L; // 示例毫秒数
Instant instant = Instant.ofEpochMilli(millis);
ZoneId zoneId = ZoneId.systemDefault(); // 获取系统默认时区
LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant, zoneId);//2024-07-02T15:33:11.609
Period 和 Duration
Period:计算两个“日期”间隔的类
Duration:计算两个“时间”间隔的类
Period 类与 Duration 类都是一段持续时间的概念,如果需要对比时间,它们就需要一个固定的时间值,所以就需要 LocalDate 、LocalDateTime、LocalTime、Instant 、类来配合它们使用
Period 对应使用 LocalDate ,它们的作用范围域都是日期(年/月/日),
Duration 对应使用 Instant、LocalTime、LocalDateTime,它们的作用范围域都是时间(天/时/分/秒/毫秒/纳秒)
// 1、传入年月日创建Period对象
Period p = Period.of(2022, 12, 31);
System.out.println("年月日:" + p);//年月日:P2022Y12M31D
// 2、传入年构造
p = Period.ofYears(2022);
System.out.println("年:" + p);//年:P2022Y
// 3、传入月份构造
p = Period.ofMonths(11);
System.out.println("月:" + p);//月:P11M
// 4、传入周构建,1周为7天
p = Period.ofWeeks(1);
System.out.println("周的天数:" + p);//周的天数:P7D
// 5、传入天数构建
p = Period.ofDays(12);
System.out.println("天数:" + p);//天数:P12D
// 6、传入负数日期
Period period = Period.of(2022, -12, 6);
// 判断日期中是否包含负数,有返回true
boolean negative = period.isNegative();
System.out.println("日期是否包含负数:" + negative);//日期是否包含负数:true
/**
* 获取年月日:
* getYears
* getMonths
* getDays
*/
// 1、传入年月日创建Period对象
Period p = Period.of(2022, 12, 31);
int years = p.getYears();
int months = p.getMonths();
int days = p.getDays();
// 如果通过 ofXXX创建,则其他值为0
Period p2 = Period.ofMonths(12);
// 年份为:0
System.out.println(p2.getYears());
// 日期为:0
System.out.println(p2.getDays());
获取两个时间差:
// 创建两个日期
LocalDate date1 = LocalDate.of(2021,11,11);
LocalDate date2 = LocalDate.of(2022,12,12);
// 获取相差多久
Period period = Period.between(date1, date2);
// 输出的默认格式为 P1Y1M1D---》P1年1月1天
System.out.println(period);//P1Y1M1D
// 通过get方法分别获取年月日
System.out.println("相差:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "日");//相差:1年1月1日
//方法一:通过ChronoUnit也可以计算两个日期之间的天数、月数或年数
// 获取相差几年 结果:1
long years = ChronoUnit.YEARS.between(date1, date2);
System.out.println(years);
// 获取相差几月 结果:13
long months = ChronoUnit.MONTHS.between(date1, date2);
System.out.println(months);
// 获取相差几天 结果:396
long days = ChronoUnit.DAYS.between(date1, date2);
System.out.println(days);
//方法二:调用LocalDate类的toEpochDay方法,返回距离1970年1月1日的long值,此方法只能计算两个LocalDate日期间的天数,不能计算月份、年数
// 通过toEpochDay将时间转换为距离1970年1月1日0时的时间,相减获取相差天数
long days2 = date2.toEpochDay() - date1.toEpochDay();
System.out.println(days2);//396
//方法三:
long daysBetween = ChronoUnit.DAYS.between(date1, date2);
System.out.println(daysBetween); // 输出: 396
// 创建两个时间
LocalDateTime start = LocalDateTime.of(2021, 11, 11, 00, 00, 00);
LocalDateTime end = LocalDateTime.of(2022, 12, 12, 12, 12, 12);
// between的用法是end-start的时间,若start的时间大于end的时间,则所有的值是负的
Duration duration = Duration.between(start, end);//PT9516H12M12S-->9516小时12分12秒
System.out.println("相差的天数=" + duration.toDays());//396
System.out.println("相差的小时=" + duration.toHours());//9516
System.out.println("相差的分钟=" + duration.toMinutes());//570972
// 获取秒,在JDK9之上才可调用,JDK8中为私有方法
//System.out.println("相差的秒数=" + duration.toSeconds());
// JDK8可以调用 getSeconds获取秒
System.out.println("相差的秒数=" + duration.getSeconds());//34258332
System.out.println("相差的毫秒=" + duration.toMillis());//34258332000
System.out.println("相差的纳秒=" + duration.toNanos());//34258332000000000
//isNegative返回Duration实例对象是否为负
//false end-start为正,所以此处返回false
System.out.println(Duration.between(start, end).isNegative());
//true start-end为负,所以此处返回true
System.out.println(Duration.between(end, start).isNegative());
//false start-start为0,所以此处为false
System.out.println(Duration.between(start, start).isNegative());
计算时间间隔:
// 方法一:通过Duration计算两个LocalTime相差的时间
LocalTime start = LocalTime.of(11, 11, 10);
LocalTime end = LocalTime.of(12, 12, 30);
Duration duration = Duration.between(start, end);
// 结果:两个时间相差:3680秒,相差:1小时,相差:61分钟
System.out.println("两个时间相差:" + duration.getSeconds() + "秒,相差:" + duration.toHours() + "小时,相差:" + duration.toMinutes() + "分钟");
// 方法二:与Period相似,通过ChronoUnit类的between() 方法来执行相同的操作
// 计算小时: 1
long hour = ChronoUnit.HOURS.between(start, end);
// 计算分钟:61
long minute = ChronoUnit.MINUTES.between(start, end);
// 计算秒: 3680
long seconds = ChronoUnit.SECONDS.between(start, end);
//方法三:通过LocalTime类的toSecondOfDay() 方法,返回时间对应的秒数,然后计算出两个时间相差的间隔
int time = end.toSecondOfDay() - start.toSecondOfDay();//3680
//计算两个时间戳的间隔:
// 获取当前时间
long todayTimeMillis = System.currentTimeMillis();
// 设置昨天时间戳【方便看结果】,可以是任意两个时间戳
long yesterdayTimeMillis = todayTimeMillis - 24 * 60 * 60 * 1000;
//通过Instant类,可以直接将毫秒值转换为Instant对象
Instant yesterday = Instant.ofEpochMilli(yesterdayTimeMillis);
Instant today = Instant.ofEpochMilli(todayTimeMillis);
Duration duration2 = Duration.between(yesterday, today);
System.out.println("天数 = " + duration2.toDays());//天数 = 1
ZonedDateTime-操作时区
ZonedDateTime类表示带时区的日期和时间。之前的日期和时间都不包含时区信息。时区的处理是新版日期和时间API新增的重要功能,使用新版日期和时间API时区的处理被极大地简化。新的java.time.ZoneId类是老版java.util.TimeZone的替代品。它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心。跟其他日期和时间类一样,ZoneId类也是无法修改的。时区是按照一定的规则将区域划分成的标准时间相同的区间。在ZoneRules这个类中包含了40个这样的实例。你可以简单地通过调用ZoneId的getRules()得到指定时区的规则。每个特定的ZoneId对象都由一个地区ID标识
//创建ZonedDateTime实例
ZonedDateTime zonedDateTime = ZonedDateTime.of(2024, 6, 14, 14, 30, 0, 0, ZoneId.of("America/New_York"));
System.out.println(zonedDateTime); // 输出: 2024-06-14T14:30-04:00[America/New_York]
//时区处理
ZonedDateTime nowInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println(nowInParis); // 输出: 当前巴黎时间 2024-07-02T10:28:49.786+02:00[Europe/Paris]
//时区转换
ZonedDateTime zonedDateTimeInTokyo = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println(zonedDateTimeInTokyo); // 输出: 转换后的东京时间 2024-06-15T03:30+09:00[Asia/Tokyo]
// 创建时区对象
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
/**地区ID都为{区域}/{城市}的格式,这些地区集合的设定都由英特网编号分配机构(IANA)的时区数据库提供。
你可以通过Java 8的新方法toZoneId将一个老的时区对象转换为ZoneId*/
// 通过TimeZone的toZoneId转换为新的时区对象
ZoneId zoneId1 = TimeZone.getDefault().toZoneId();
//一旦得到ZoneId对象,就可以将它与LocalDate、LocalDateTime或者是Instant对象整合起来,构造为一个ZonedDateTime实例,它代表了相对于指定时区的时间点
// 创建时区
LocalDate date=LocalDate.of(2022, 12, 12);
// 根据 LocalDate 获取 ZonedDateTime
ZonedDateTime zdt1=date.atStartOfDay(zoneId);
System.out.println(zdt1);//2022-12-12T00:00+08:00[Asia/Shanghai]
LocalDateTime dateTime=LocalDateTime.of(2022, 12, 12, 13, 14);
// 根据 LocalDateTime 获取 ZonedDateTime
ZonedDateTime zdt2=dateTime.atZone(zoneId);
System.out.println(zdt2);//2022-12-12T13:14+08:00[Asia/Shanghai]
Instant instant=Instant.now();
// 根据 Instant 获取 ZonedDateTime
ZonedDateTime zdt3=instant.atZone(zoneId);
System.out.println(zdt3);//2024-07-02T17:43:37.282+08:00[Asia/Shanghai]
重要:LocalDate、LocalTime、ZoneId、LocalDateTime、ZonedDateTime五者关系如下:
格式化日期为特定格式的字符串
Java中DateTimeFormatter的使用方法和案例
在DateTimeFormatter.ofPattern()方法的参数中,你需要传入一个日期时间模式字符串,用于指定日期时间的格式。
日期时间模式字符串中可以包含以下字符:
LocalDate localDate = LocalDate.now();
// 通过 LocalDate 的 format方法根据传入的 DateTimeFormatter类中的常量【也就是时间格式】进行格式化
String format1 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
String format2 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(format1);//2024-07-02
System.out.println(format2);//20240702
LocalDateTime now = LocalDateTime.now();
System.out.println(now);//2024-07-02T17:49:17.623
System.out.println(now.format(DateTimeFormatter.BASIC_ISO_DATE));//20240702
System.out.println(now.format(DateTimeFormatter.ISO_LOCAL_DATE));//2024-07-02
System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME));//2024-07-02T17:49:17.623
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(now.format(dateTimeFormatter));//2024-07-02 17:49:17
//也可以通过工厂方法解析字符串来创建 LocalDate
LocalDate localDate2 = LocalDate.parse("20240702", DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(localDate2);//2024-07-02
//字符串转换为时间日期
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.parse("2023-04-07 10:10:10", formatter);
System.out.println(localDateTime);//2023-04-07T10:10:10
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate localDate = LocalDate.parse("2023-04-09", formatter2);
System.out.println(localDate);//2023-04-09
//创建对象
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
// 设置格式化
DateTimeFormatter formatter = builder.appendLiteral("今天是:")
.appendValue(ChronoField.YEAR)
.appendLiteral("年,")
.appendValue(ChronoField.MONTH_OF_YEAR)
.appendLiteral("月,")
.appendValue(ChronoField.DAY_OF_MONTH)
.appendLiteral("日,周")
.appendValue(ChronoField.DAY_OF_WEEK)
.toFormatter();
LocalDateTime dateTime = LocalDateTime.now();
// 使用格式化对象
String str = dateTime.format(formatter);
System.out.println(str);//今天是:2024年,7月,2日,周2
LocalDateTime在SpringBoot中的应用
将LocalDateTime字段以时间戳的方式返回给前端 添加日期转化类
public class LocalDateTimeConverter extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(
LocalDateTime value,
JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeNumber(value.toInstant(ZoneOffset.of("+8")).toEpochMilli());
}
}
并在 LocalDateTime 字段上添加 @JsonSerialize(using = LocalDateTimeConverter.class) 注解,如下:
@JsonSerialize(using = LocalDateTimeConverter.class)
protected LocalDateTime gmtModified;
将LocalDateTime字段以指定格式化日期的方式返回给前端
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;
对前端传入的日期进行格式化
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;
前后端日期时间转化问题
在实体类上加@DatetimeFormat与@JsonFormat注解
@DatetimeFormat 将前台日期字符串转换成Date格式 @DateTimeFormat(pattern="yyyy-MM-dd")
@JsonFormat 将服务器端Date日期转换成指定字符串格式 @JsonFormat(pattern="yyyy-MM-dd",timezone="GMT+8")
两个需要同时加,否则会有时区的问题
函数式接口(Functional)
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。
- 我们可以在一个接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc也会包含一条声明,说明这个接口是一个函数式接口。
- 在java.util.function包下定义了Java 8 的丰富的函数式接口