Java8新特性之Optional、Lambda表达式、Stream、新日期Api使用总结

标题

  • 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的延迟执行特性

特性说明:

  1. 【使用步骤】使用一个Stream流,一般分为三个步骤:
    1.获取数据源-> 2. 中间操作(Intermediate)-> 3. 终端操作(Terminal)。

  2. 【操作分类】操作分为中间操作与终端操作
    一个中间操作链,对数据源的数据进行处理,比如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 的丰富的函数式接口
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/765361.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux从入门到放弃】探究进程如何退出以进程等待的前因后果

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《Linux从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; 进…

【C++】STL-priority_queue

目录 1、priority_queue的使用 2、实现没有仿函数的优先级队列 3、实现有仿函数的优先级队列 3.1 仿函数 3.2 真正的优先级队列 3.3 优先级队列放自定义类型 1、priority_queue的使用 priority_queue是优先级队列&#xff0c;是一个容器适配器&#xff0c;不满足先进先出…

pdf拆分,pdf拆分在线使用,pdf拆分多个pdf

在数字化的时代&#xff0c;pdf文件已经成为我们日常办公、学习不可或缺的文档格式。然而&#xff0c;有时候我们可能需要对一个大的pdf文件进行拆分&#xff0c;以方便管理和分享。那么&#xff0c;如何将一个pdf文件拆分成多个pdf呢&#xff1f;本文将为你推荐一种好用的拆分…

监控平台zabbix介绍与部署

目录 1.为什么要做监控 2.zabbix是什么&#xff1f; 3.zabbix 监控原理 4.Zabbix 6.0 新特性 5.Zabbix 6.0 功能组件 6.部署zabbix 6.1 部署 Nginx PHP 环境并测试 6.2 部署数据库 6.3 向数据库导入 zabbix 数据 6.4 编译安装 zabbix Server 服务端 6.5 修改 zabbix…

中小企业如何防止被查盗

在当前的商业环境中&#xff0c;小企业面临诸多挑战&#xff0c;其中之一便是如何在有限的预算内满足日常运营的技术需求。由于正版软件的高昂成本&#xff0c;一些小企业可能会选择使用盗版软件来降低成本。 我们联网之后存在很多风险&#xff0c;你可以打开自己的可以联网的电…

【面试系列】机器学习工程师高频面试题及详细解答

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&#xff1a;详细讲解AIGC的概念、核心技术、…

Java--创建对象内存分析

1.如图所示&#xff0c;左边为一个主程序&#xff0c;拥有main方法&#xff0c;右边定义了一个Pet类&#xff0c;通过debug不难看出&#xff0c;当启动该方法时&#xff0c;有以下该步骤 1.运行左边的实例化Pet类对象 2.跳转至右边定义Pet类的语句 3.跳回至左边获取Pet类基本属…

代码生成器使用指南,JeecgBoot低代码平台

JeecgBoot 提供强大的代码生成器&#xff0c;让前后端代码一键生成&#xff0c;实现低代码开发。支持单表、树列表、一对多、一对一等数据模型&#xff0c;增删改查功能一键生成&#xff0c;菜单配置直接使用。 同时提供强大模板机制&#xff0c;支持自定义模板&#xff0c;目…

【bug报错已解决】ERROR: Could not find a version that satisfies the requirement

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言一、问题描述1.1 报错示例1.2 报错分析 二、解决方法2.1 方法一2.2 方法二 三、总结 引言 有没有遇到过那种让人…

JSON JOLT常用示例整理

JSON JOLT常用示例整理 1、什么是jolt Jolt是用Java编写的JSON到JSON转换库&#xff0c;其中指示如何转换的"specification"本身就是一个JSON文档。以下文档中&#xff0c;我统一以 Spec 代替如何转换的"specification"json文档。以LHS(left hand side)代…

kaggle量化赛金牌方案(第七名解决方案)

获奖文章(第七名解决方案) 致谢 我要感谢 Optiver 和 Kaggle 组织了这次比赛。这个挑战提出了一个在金融市场时间序列预测领域中具有重大和复杂性的问题。 方法论 我的方法结合了 LightGBM 和神经网络模型,对神经网络进行了最少的特征工程。目标是结合这些模型以降低最终…

arco disign vue 日期组件的样式穿透

问题描述: 对日期组件进行样式穿透. 原因分析: 如图,日期组件被展开时它默认将dom元素挂载到body下, 我们的页面在idroot的div 里层, 里层想要穿透外层是万万行不通的. 解决问题: 其实官网提供了参数,但是并没有提供例子, 只能自己摸索着过河. 对于日期组件穿透样式,我们能…

收集了很久的全网好用的磁力搜索站列表分享

之前找资源的时候&#xff0c;收集了一波国内外大部分主流的磁力链接搜索站点。每一个站可能都有对应的优缺点&#xff0c;多试试&#xff0c;就能知道自己要哪个了。 全网好用的磁力链接 大部分的时候&#xff0c;我们用国内的就可以了&#xff0c;速度块&#xff0c;而且不…

Snappy使用

Snappy使用 Snappy是谷歌开源的压缩和解压的开发包&#xff0c;目标在于实现高速的压缩而不是最大的压缩 项目地址&#xff1a;GitHub - google/snappy&#xff1a;快速压缩器/解压缩器 Cmake版本升级 该项目需要比较新的cmake&#xff0c;CMake 3.16.3 or higher is requi…

51单片机第23步_定时器1工作在模式0(13位定时器)

重点学习51单片机定时器1工作在模式0的应用。 在51单片机中&#xff0c;定时器1工作在模式0&#xff0c;它和定时器0一样&#xff0c;TL1占低5位&#xff0c;TH1占高8位&#xff0c;合计13位&#xff0c;也是向上计数。 1、定时器1工作在模式0 1)、定时器1工作在模式0的框图…

8619 公约公倍

这个问题可以通过计算最大公约数 (GCD) 和最小公倍数 (LCM) 来解决。我们需要找到一个整数&#xff0c;它是 a, b, c 的 GCD 的倍数&#xff0c;同时也是 d, e, f 的 LCM 的约数。 以下是解决这个问题的步骤&#xff1a; 1. 计算 a, b, c 的最大公约数。 2. 计算 d, e, f 的最…

流处理系统对比:RisingWave vs ksqlDB

本文将从架构、部署与可扩展性、Source 和 Sink、生态系统与开发者工具几个方面比较 ksqlDB 和 RisingWave 这两款领先的流处理系统。 1. 架构 ksqlDB 是由 Confluent 开发和维护的流处理 SQL 引擎&#xff0c;专为 Apache Kafka 设计。它基于 Kafka Streams 构建&#xff0c;…

鸿蒙:路由Router原理

页面路由&#xff1a;在应用程序中实现不同页面之间的跳转和数据传递 典型应用&#xff1a;商品信息返回、订单等多页面跳转 页面栈最大容量为32个页面&#xff0c;当页面需要销毁可以使用router.clear()方法清空页面栈 router有两种页面跳转模式&#xff1a; router.pushUrl…

Golang 开发实战day15 - Input info

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 开发实战day15 - 用户…

02归并排序——分治递归

02_归并排序_——分治_递归_ #include <stdio.h>void merge(int arr[], int l, int m, int r) {int n1 m -l 1;int n2 r -m;//创建临时数组int L[n1], R[n2];for(int i 0; i < n1; i){L[i] arr[l i];}for(int j 0; j < n2; j){R[j] arr[m 1 j];}int i …