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

news2024/11/19 20:37:41

标题

  • 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 的丰富的函数式接口
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

核心实验:基于Web前端的性能测试分析!

实验简介 本实验主要利用IE和Chrome的F12开发人员工具结合Web前端测试分析相关知识&#xff0c;对常见网站进行基于前端的性能测试分析&#xff0c;本实验将不会使用到测试开发相关技术&#xff0c;而是纯粹意义上的手工测试&#xff0c;但却是很容易找到系统前端性能及设计问…

什么是继电器测试负载箱?

继电器测试负载箱专门用于测试继电器性能的设备&#xff0c;它能够模拟各种实际工况下的负载&#xff0c;以便对继电器进行全面、准确的性能评估。继电器是一种广泛应用于电力系统、自动化控制系统等领域的关键元件&#xff0c;其主要功能是在输入信号的控制下&#xff0c;实现…

耐克:老大的烦恼

股价暴跌20%&#xff0c;老大最近比较烦。 今天说说全球&#xff08;最&#xff09;大运动品牌——耐克。 最近耐克发布2023-2024财年业绩&#xff08;截止于2024.5.31&#xff09;&#xff0c;还是爆赚几百亿美元&#xff0c;还是行业第一&#xff0c;但业绩不及预期&#xf…

不同node版本的切换及其指定版本vue-cli脚手架下载

目录 一.清空本地已安装node.js版本 二.装nvm管理工具 三.安装指定node版本 四.使用nvm命令切换或删除指定node版本 五.在指定node版本下下载指定vue-cli脚手架 一.清空本地已安装node.js版本 1.按健winR弹出窗口&#xff0c;键盘输入cmd&#xff0c;然后敲回车。 2.输入…

对比学习,新的顶会神器!2024最新SOTA&应用盘点!

【对比学习&#xff0c;Contrastive Learning】是一种自监督学习方法&#xff0c;通过将视觉表示拉近相似样本、推远不相似样本来学习特征。它不依赖于标签&#xff0c;利用数据增强和负样本采样来提升特征的区分性。对比学习在提高模型对不同数据变化的鲁棒性方面表现出色&…

A股站不稳3000点让人稀罕不已啊

今天的A股&#xff0c;让人稀罕不已&#xff0c;你知道是为什么吗&#xff1f;盘面出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市冲了下3000点&#xff0c;第一个主题炒作的热点终于出现了&#xff0c;税改方向的行情发酵&#xff0c;并带动着其他改革相关方…

Git 安装

Git 安装 在使用 Git 前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平台上运行。Git 各平台安装包下载地址为&#xff1a;http://git-scm.com/downloads 在 Linux 平台上安装&#xff08;包管理工具安装&#xff09; 首先&#xff0c;你可以试着输…

Splashtop 致力于增强远程访问的安全性

数字时代&#xff0c;网络安全是企业运营的基石。随着远程工作模式的普及&#xff0c;企业越来越依赖远程访问解决方案。这既带来了灵活性和生产力的提升&#xff0c;也增加了安全风险。 最近接二连三发生的安全事件&#xff0c;凸显了强化安全措施的必要性。Splashtop 深知安…

Upload-Labs靶场闯关

文章目录 Pass-01Pass-02Pass-03Pass-04Pass-05Pass-06Pass-07Pass-08Pass-09Pass-10Pass-11Pass-12Pass-13Pass-14Pass-15Pass-16Pass-17Pass-18Pass-19Pass-20 以下是文件上传绕过的各种思路&#xff0c;不过是鄙人做题记下来的一些思路笔记罢了。 GitHub靶场环境下载&#x…

Mac|install vue

安装Node&#xff1a;Node.js — Download Node.js 选择系统为mac&#xff0c;安装步骤在终端输入 &#xff08;放文字版在这里&#xff5e;方便复制&#xff09; # installs nvm (Node Version Manager) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/ins…

WebSocket解决方案(springboot 基于Redis发布订阅)

WebSocket 因为一般的请求都是HTTP请求&#xff08;单向通信&#xff09;&#xff0c;HTTP是一个短连接&#xff08;非持久化&#xff09;&#xff0c;且通信只能由客户端发起&#xff0c;HTTP协议做不到服务器主动向客户端推送消息。WebSocket确能很好的解决这个问题&…

【ARM系列】GIC600AE功能安全

GIC600AE在原GIC600版本基础上增加了FuSa功能&#xff0c;所增加的FuSa特性都集成在GIC600外围&#xff0c;不会改变原GIC600的功能。 GIC600AE主要安全机制分布图&#xff1a; GIC-600AE包含以下FuSa安全机制&#xff1a; lockstep logic protection 通过添加duplication l…

数值治理学习记录

添加链接描述 数值清洗规则 数据质量问题 数据治理校验规则 数据探查分析 数据质量规则指标 转自&#xff1a;公众号《数据治理体系》

嵌入式c语言1——gcc以及linux嵌入式

GCC全名GNU Complier Collection&#xff0c;是一个开源的程序语言解释器&#xff0c;运行在linux系统中 对以程序名后缀结尾源代码文件&#xff0c;gcc可以做解释并生成可执行文件

贪心算法算法,完全零基础小白教程,不是计算机的都能学会!超详解

目录 一、基本概念 二、举几个例子&#xff0c;便于理解 1、找零问题 2、最小路径和 3、背包问题 1&#xff09;只考虑体积的贪心策略&#xff1a; 2&#xff09; 只考虑价值的贪心策略&#xff1a; 三、贪心策略的特点 四、贪心策略证明 四、如何学习贪心 五、例题…

Unity海面效果——4、法线贴图和高光

Unity引擎制作海面效果 大家好&#xff0c;我是阿赵。 继续做海面效果&#xff0c;上次做完了漫反射颜色和水波动画&#xff0c;这次来做法线和高光效果。 一、 高光的计算 之前介绍过高光的光照模型做法&#xff0c;比较常用的是Blinn-Phong 所以我这里也稍微连线实现了一下 …

问题-小技巧-Win11的常用快捷方式和有用快捷方式

文章目录 常用快捷方式1、CtrlA 全部选中2、Ctrl Z 撤销3、Ctrl X 剪切4、Ctrl C 粘贴5、Ctrl V 复制6、winshifts截图&#xff0c;Windows系统自带截图工具&#xff0c;功能太少7、ctrlshifts截图&#xff0c;edge自带截图工具&#xff0c;使用时需要打开edge8、 winv 可以查看…

实习总结 --- 内部平台使用

常用术语 CR CR–标准问题分类管理平台&#xff1a;由业务类型-角色-国家-品类-Page定义。 FAQSOP FAQ是端上用户自助的第一道关口&#xff0c;在引导用户进行自助解决上起关键作用 SOP是指标准作业程序&#xff0c;客服SOP是针对用户遇到的具体问题场景&#xff0c;给客服…

编写静态库

一、静态库 1.制作完成整体目录结构 2.首先创建mymath.c和mymath.h 3.编写Makefile 4.创建测试的main函数 test文件夹 先把lib移到test文件夹里面 4.编译链接 gcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -l mymath 5.形成可执行程序a.out 要是不想执行第四步那么麻烦…

揭秘软件性能测试方法和注意事项,专业软件测试公司分享

目前&#xff0c;随着云计算和大数据的发展&#xff0c;软件的性能需求越来越高&#xff0c;用户对于软件的性能体验也有了更高的期望。因此&#xff0c;进行软件性能测试不仅是一种需求&#xff0c;更是一种责任&#xff0c;是开发过程中必不可少的一环。软件性能测试&#xf…