Java8补充

警告
本文最后更新于 2023-03-24,文中内容可能已过时,请谨慎使用。
Java 8实战
豆瓣评分
9.0
本书全面介绍了Java 8 这个里程碑版本的新特性,包括Lambdas、流和函数式编程。有了函数式的编程特性,可以让代码更简洁,同时也能自动化地利用多核硬件。全书分四个部分:基础知识、函数式数据处理、高效Java 8 编程和超越Java 8,清晰明了地向读者展现了一幅Java 与时俱进的现代化画卷。

Lambda 由三部分组成,分别是参数列表、箭头、函数体

参数列表

  • 和普通函数一样,Lambda 也有参数列表,且表达方式相同,如:(ParamType1 param1, ParamType2 param2, ...)
  • 可以省略参数列表参数类型,如(param1, param2, ...)
  • 如果参数列表只有一个,且省略了参数类型,可以省略(),如param1
  • 参数列表没有参数,()不能省略

箭头

Lambda 表达式的参数列表后紧跟关的是箭头,如->

函数体

  • 和普通函数一样,Lambda 也有函数体,如{ System.out.println("hello world!");}
  • 如果函数体只有一条语句,可以省略{},如System.out.println("hello world!")}
  • 如果函数体只有一条语句,且函数有返回值,要省略return关键字。
public class LambdaTest {
    @Test
    void test1() {
        // 1.和普通函数一样,Lambda也有参数列表,且表达方式相同,如:(ParamType1 param1, ParamType2 param2, ...)
        Comparator<Integer> comparator = (Integer a, Integer b) -> {
            return a - b;
        };
        int compare = comparator.compare(1, 2);
        System.out.println("compare = " + compare);
    }

    @Test
    void test2() {
        // 2.可以省略参数列表参数类型,如(param1, param2, ...)
        Comparator<Integer> comparator = (a , b) -> {
            return a - b;
        };
        int compare = comparator.compare(1, 2);
        System.out.println("compare = " + compare);
    }

    @Test
    void test3() {
        // 3.如果参数列表只有一个,且省略了参数类型,可以省略(), 如param1
        Predicate<Integer> p = a -> {
            return a > 10;
        };
        boolean result = p.test(11);
        System.out.println(result);
    }

    @Test
    void test4() {
        // 4.参数列表没有参数,()不能省略
        // 5.如果函数体只有一条语句,可以省略{}
        Runnable runnable = () ->
            System.out.println("hello");
        runnable.run();
    }

    @Test
    void test5() {
        // 6.如果函数体只有一条语句,且函数有返回值,要省略return关键字
        Predicate<Integer> p = a -> a > 10;
        boolean result = p.test(100);
        System.out.println("result = " + result);
    }
}

特殊的void兼容规则

如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。例如,以下Lambda是合法的,尽管List的add方法返回了一个 boolean,而不是Runnable抽象方法函数描述符() -> void所要求的void:

List<String> list = new ArrayList<>();
Runnable r = () -> list.add("hello");

外部变量指的是不属性于当前函数或者类的变量,比如 Lambda 表达式引入一个没有在 Lambda 表达式中定义的变量,匿名类引用一个没在该类中定义的变量,这些变量都属性外部变量。

使用匿名类,在Java 1.8之前引用的外部变量必须是 final 类型的,在 Java 1.8 之后,外部变量可以不用使用final关键字,但是引用的外部变量不允许被改变(无论是 lambda 函数体内还是外面)。

Lambda 相当于匿名类,所以对于 Lambda 表达式要使用外部变量也有同样的限制。

@Test
public void test6() {
    String msg = "message";

    // msg = "hello"; // 不允许被修改
    Runnable r = () -> {
        // msg = "hello"; // 不允许被修改
        System.out.println(msg);
    };
    // msg = "hello"; // 不允许被修改
    r.run();
}
  • @FunctionalInterface注解可以鉴定当前接口是否是函数式接口
  • 函数式接口只有一个抽象方法,但是允许有默认的实现(使用 default 关键字描述方法)
  • 接口重写了 Object的公共方法不算入内
  • Lambda表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例

例如前面使用的Runnable就是一个函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
函数式接口 函数描述符 参数 返回类型
Predicate<T> bolean test(T t) T boolean
Comsumer<T> void accept(T t) T void
Function<T, R> R apply(T t) T R
Supplier<T> T get() T

示例:

@Test
public void test7() {
    Predicate<Integer> predicate = a -> a > 10;
    boolean result = predicate.test(10);
    System.out.println(result);

    Consumer<String> consumer = str -> System.out.println(str);
    consumer.accept("hello");


    Function<String, BigDecimal> function = s ->  new BigDecimal(s);
    BigDecimal number = function.apply("1.5656");
    System.out.println(number);

    Supplier<String> supplier = () -> "hello";
    System.out.println(supplier.get());

    IntPredicate intPredicate = a -> a > 20;
    boolean result1 = intPredicate.test(100);
    System.out.println(result1);

}

上述 4 个函数式接口,是最常用的几个函数式接口。

关于装箱拆箱

Predicate<T>中的 T 是泛型,泛型不能接收基本类型,但是使用包装类型会增加内存和计算开销,所以对于基本类型 Java 8 中也提供了对应的函数式接口,基本类型的函数式接口主要有:

函数式接口 函数描述符
IntPredicate boolean test(int value);
IntConsumer void accept(int value);
IntFunction R apply(int value);
IntSupplier int getAsInt();

int类型为例的函数式接口如上,其他类型类同 。要注意,基本类型的函数式接口只针对常用的longintdouble。其他基本类型没有对应的函数式接口。

在 Java 8 之前,接口中的所有方法必须是抽象方法。实现该接口的类都必须实现接口中所有抽象方法,除非这个类是抽象类。

Java 8 中使用了一个 default 关键字用于在接口中定义默认方法,该方法有方法体,如果实现该接口的类没有“实现”(重写)该方法,那么实现类将使用默认方法,如果实现类中“实现”(重写)了该方法,实现类中的该方法将覆盖接口中的默认方法。

例如 Java 8 中的 stream 方法。Java 8 中为集合 Collection 添加了 stream()方法用于获取流,该 stream 方法就是一个抽象方法。如果不添加默认方法,而是添加 stream()抽象方法,自定义的实现了 Collection 的类也要实现该抽象方法。如果自定义的类是在 Java 8 之前就已经有了,那么 Java 版本升级到 Java 8 之后,所有的自定义的 Collection 实现类都得实现该抽象方法。如果项目中引用的第三方库中的集合类,就会出现版本不兼容情况。

在 Collection 中使用 default 关键字的例子:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

注意:

  1. 接口中的默认方法,子类(接口继承、实现)如果重写了,子类的优先级更高。

  2. 实现多接口,接口中有相同的默认方法需要指明使用哪一个接口中定义的默认方法

  3. 子类不使用接口中的默认方法需将该方法覆盖且为抽象方法

public class DefaultMethodTest {
    public static void main(String[] args) {
        new D().m1();
    }

}

interface A {
    default void m1() {
        System.out.println("A");
    }
}

interface B {
    default void m1() {
        System.out.println("B");
    }
}

class C implements B{
    // 调用接口B中的默认实现
}

class D implements A,B{
    @Override
    public void m1() {
        // 可以指定使用哪个接口的默认方法
        // A.super.m1();
        // B.super.m1();
        // 也可以自己重新实现
        System.out.println("D");
    }
}

// 不适用之前的接口的默认方法
abstract class E implements A,B {
    @Override
    public abstract void m1();
}

Java 8 中,接口也能添加静态方法

public class StaticMethodTest {
    public static void main(String[] args) {
        // 和普通类调用静态方法一样
        test.greet();
    }
}

interface test {
    static void greet() {
        System.out.println("hello");
    }
}

方法引用可以被看作仅仅调用特定方法的 Lambda 的一种快捷写法。它的基本思想是,如果一个 Lambda 代表的只是直接调用这个方法,那最好还是用名称来调用它,而不是去描述如何调用它,这样代码可读性更好。

基本写法就是目标引用放在分隔符::前,方法的名称放在后面。

主要包括下面三种:

  • 构造器引用: 它的语法是Class::new
  • 静态方法引用: 它的语法是Class::static_method_name
  • 实例方法引用: 它的语法是instance::method_name

Apple类:

public class Apple {
    public Apple() {
    }

    public Apple(String color) {
        this.color = color;
    }
    private String color;

    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }

    /**
     * 仅测试使用,无任何意义
     * 测试静态方法的方法引用
     * @param str1 字符串1
     * @param str2 字符串2
     */
    public static void printStr(String str1,String str2) {
        System.out.println(str1 + "*****" + str2);
    }

    /**
     * 仅测试使用,无任何意义
     * @param str 字符串
     */
    public void print(String str) {
        System.out.println(str);
    }
}

自定义函数式接口:

Generator

@FunctionalInterface
public interface Generator<T, R> {
    R create(T t);
}

Generator1:

@FunctionalInterface
public interface Generator1<T> {
    T create();
}

测试类:

package cc.bnblogs.java8.InterfacceMethod;

import cc.bnblogs.java8.lambda.Apple;
import cc.bnblogs.java8.lambda.Generator;
import cc.bnblogs.java8.lambda.Generator1;
import org.junit.jupiter.api.Test;

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class MethodReference {
    /**
     * 构造器方法引用
     */
    @Test
    void test1() {
        // 调用有参构造
        Generator<String, Apple> g = color -> new Apple(color);
        Apple apple = g.create("red");
        System.out.println(apple.getColor());

        Generator<String,Apple> g1 = Apple::new;
        Apple apple1 = g1.create("green");
        System.out.println(apple1.getColor());

        // 调用无参构造器
        Generator1<Apple> a1 = Apple::new;
        Apple apple2 = a1.create();
        apple2.setColor("yellow");
        System.out.println(apple2.getColor());

        // 可以使用自带的函数式接口
        // 效果和上面的一样
        // Supplier<Apple> supplier = () -> new Apple();
        Supplier<Apple> supplier = Apple::new;
        Apple apple3 = supplier.get();
        apple3.setColor("red_green");
        System.out.println(apple3.getColor());

//        Function<String,Apple> function = color -> new Apple(color);
        Function<String,Apple> function = Apple::new;
        Apple apple4 = function.apply("green_yellow");
        System.out.println(apple4.getColor());

    }

    /**
     * 静态方法引用
     */
    @Test
    void test2() {

        BiConsumer<String,String> biConsumer = (str1,str2) -> Apple.printStr(str1,str2);
        biConsumer.accept("ttt","yyy");
        BiConsumer<String,String> biConsumer1 = Apple::printStr;
        biConsumer1.accept("aaaa","bbbb");
    }

    /**
     * 实例方法引用
     */
    @Test
    void test3() {
        Apple apple = new Apple("red");
        apple.print("hello1");
        // 不使用方法引用
        Consumer<String> consumer = s -> apple.print(s);
        consumer.accept("hello2");

        // 使用方法引用
        Consumer<String> consumer1 = apple::print;
        consumer1.accept("hello3");
    }
}

Optional源码

public final class Optional<T> {

    private static final Optional<?> EMPTY = new Optional<>();

    private final T value;

	/*
		无参构造
	 */
    private Optional() {
        this.value = null;
    }

    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
	/*
		有参构造
	 */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    public T orElse(T other) {
        return value != null ? value : other;
    }

    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
    // ...
}

通过源码可以看到 Optional 中有两个属性,分别是 EMPTY 和 value,EMPTY 默认初始化一个 value 值为 null 的 Optional 对象。value 就是 Optional 容器存储的具体数据。

注意 Optional 的构造器是 private,所以不能通过 new 创建对象的方式来创建 Optional 对象

对应各基本数据类型的 Optional,分别是 OptionalInt、OptionalDouble 和 OptionalLong。

  • empty:获取一个valuenullOptional对象
  • of:创建一个不为nullOptional对象,该方法接收一个参数作为value值,该参数不能为null
  • ofNullable:创建一个可以为nullOptional对象,该方法接收一个参数作为value值,该参数可以为null
public class OptionalTest {

    private Employee employee = new Employee(1L,"tom",38,100000.0);

    @Test
    void test1() {
        // 三种创建方式
        // of不允许传入null
        Optional<Employee> employeeOptional = Optional.of(employee);
        // 创建一个value=null的Optional对象
        Optional<Employee> empty = Optional.empty();
        // 允许传入null值,非null当然也可以
        Optional<Employee> employeeOptional1 = Optional.ofNullable(null);
        Optional<Employee> employeeOptional2 = Optional.ofNullable(employee);

        System.out.println(employeeOptional);
        System.out.println(empty);
        System.out.println(employeeOptional1);
        System.out.println(employeeOptional2);
    }
}
  • get:用于获取Optional中的value的值

如果valuenull,直接使用get获取value则抛出一个java.util.NoSuchElementException: No value present的异常。

所以在使用get前最好判断一下value是否存在!

@Test
void test2() {
    Optional<Employee> employee1 = Optional.of(employee);
    Employee employee2 = employee1.get();
    //        System.out.println(employee1); // 直接输入employee1,会调用Optional的toString方法
    System.out.println(employee2);

    Optional<Employee> empty = Optional.empty();
    Employee employee3 = empty.get();
    System.out.println(employee3);

}
  • isPresent:判断Optionalvalue属性是否为null,如果不为null,则返回true,反之则返回false
  • ifPresent:该方法接收一个Comsumer类型参数,用于判断值是否存在,如果存在就执行Comsumer操作
@Test
void test3() {
    Optional<Employee> employee1 = Optional.of(employee);
    Optional<Object> empty = Optional.empty();

    System.out.println("employee1.isPresent() = " + employee1.isPresent());
    System.out.println("empty.isPresent() = " + empty.isPresent());

    employee1.ifPresent(e-> System.out.println(e));
}
  • filter:过滤值。该方法接收一个Predicate参数,如果Optional中的值满足参数条件,就返回该Optional,否则返回空的Optional,如果原来Optional中值为null,新生成的Optional中值也为null

  • map:操作值返回新值。该方法上接收一个Function类型对象,接收Optional中的值,生成一个新值再包装成Optional并返回,如果原来Optional中值为null,新生成的Optional中值也为null

  • flatMap:如果有值,为其执行Function函数返回Optional类型返回值,否则返回空OptionalflatMapmap方法类似,区别在于flatMap中的Function函数返回值必须是Optional

@Test
void test4() {
    // 操作值
    Optional<Employee> employee1 = Optional.of(employee);
    Optional<Employee> empty = Optional.empty();

    System.out.println(employee1.filter(e -> e.getAge() > 30));
    System.out.println(employee1.filter(e -> e.getAge() < 30));
    System.out.println(employee1.filter(e->e.getAge() < 30));


    System.out.println(employee1.map(e -> e.getName()));
    System.out.println(employee1.flatMap(e->Optional.ofNullable(e.getName())));

}
  • orElse:当Optional中的值为null时,返回该方法接收的参数
  • orElseGet:该方法接收一个Supplier参数,用于在Optional中的值为null时返回Supplier生成的值
  • orElseThrow:该方法接收一个Supplier参数,该Supplier生成一个异常,用于在Optional中的值为null时抛出该异常
@Test
void test5() throws Exception {
    // 操作空值
    Optional<Employee> employee1 = Optional.of(employee);
    Optional<Employee> empty = Optional.empty();

    System.out.println(employee1.orElse(new Employee(2L,"bob",20,999.88)));
    System.out.println(empty.orElse(new Employee(2L,"bob",20,999.88)));

    System.out.println(employee1.orElseGet(() -> new Employee(2L,"bob",20,999.88)));
    System.out.println(empty.orElseGet(() -> new Employee(2L,"bob",20,999.88)));
    // value为null时抛出一个异常
    Employee employee2 = empty.orElseThrow(() -> new Exception());
}

Stream是 Java 8 中新增用于操作集合的 API,使用Stream API 可以使我们对集合的操作只需要关注数据变化 ,而不是迭代过程。

@Test
public void test01() {
  List<Employee> employees = DataUtils.getEmployees();
  List<Employee> result = employees.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
}

没有使用for循环,不再关注迭代过程,这个需求中我们只需要关注的是筛选条件年龄大于 30filter(x -> x.getAge() > 30)和结果组成新的集合collect(Collectors.toList())

获取流后,对该流的操作是一次性的,也就是该流只能被操作一次,如果已经被操作的流再次操作,将抛出异常:

List<Employee> employees = DataUtils.getEmployees();
@Test
void test1() {
    Stream<Employee> stream = employees.stream();
    List<Employee> result1 = stream.filter(e -> e.getAge() > 30).collect(Collectors.toList());
    // java.lang.IllegalStateException: stream has already been operated upon or closed
    List<Employee> result2 = stream.filter(e -> e.getSalary() > 10000).collect(Collectors.toList());
}

该实现机制涉及两个概念,分别是惰性求值及早求值

惰性求值在执行代码时,实际上操作并没有执行,看下面这个例子

@Test
void test2() {
    Stream<Employee> employeeStream = employees.stream().filter(e -> {
        System.out.println(e.getName());
        return e.getAge() > 30;
    });
}

执行上面的代码,我们会发现System.out.println(e.getName());并没有执行,所以我们称filter惰性求值操作,惰性求值期间不会执行相应代码操作。最终结果返回一个Stream流。

及早求值在执行时最终会生成相应结果,执行惰性求值操作,而不是返回 Stream,看下面的例子:

@Test
void test2() {
    Stream<Employee> employeeStream = employees.stream().filter(e -> {
        System.out.println(e.getName());
        return e.getAge() > 30;
    });

    System.out.println("优先执行");

    long count = employeeStream.count();
    System.out.println(count);
}

输出结果:

优先执行
Morton
... // 省去中间的8个name
Calderon
5

执行上面的代码,我们会发现System.out.println("优先执行");最先执行,接着执行System.out.println(e.getName());,所以我们称count及早求值操作,及早求值期间会先执行惰性求值,并得到最终结果

  • of:传入任意个数同类型的值
  • iterate:通过迭代生成对应的流,第一个参数seed为初始值,第二个参数f为迭代过程,将生成无限流,需要配合limit使用
  • Arrays.stream(): 传入一个数组生成流
  • generate:传入一个Supplier对象生成流
@Test
void test3() {
    Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
    s1.forEach(System.out::println);
    System.out.println("-------------------------");
    // limit限制生成的流的数据的总数
    Stream<Integer> limit = Stream.iterate(0, x -> x + 2).limit(10);
    limit.forEach(System.out::println);
    System.out.println("-------------------------");

    String[] strs = {"aaa","bbb","ccc"};
    Stream<String> stream = Arrays.stream(strs);
    stream.forEach(System.out::println);
    System.out.println("-------------------------");

    Stream<Double> limit1 = Stream.generate(() -> Math.random() * 10).limit(10);
    limit1.forEach(System.out::println);
}

中间操作是惰性求值

  • filter:接收一个Predicate参数,根据Predicate中定义的判断条件筛选流中元素
  • distinct:筛选出流中重复的元素并剔除,使集合中的元素不重复(去重)
@Test
void test4() {
Stream<Employee> stream = employees.stream();
// 按条件过滤数据
List<Employee> result1 = stream.filter(e -> e.getAge() > 30)
.collect(Collectors.toList());
System.out.println(result1);

// 去重
List<Employee> result2 = employees.stream().distinct().collect(Collectors.toList());
System.out.println(result2);

}
  • limit:接收一个long类型数值,返回不超过该指定数值长度的流
  • skip:接收一个long类型的数值,返回除指定数值长度元素以外的流,和limit相反
@Test
void test5() {
    Stream<Employee> stream = employees.stream();
    List<Employee> result1 = stream.filter(e -> e.getAge() > 30)
        .collect(Collectors.toList());
    System.out.println(result1);

    Stream<Employee> stream1 = employees.stream();
    // 跳过前三个数据
    Set<Employee> result2 = stream1.skip(3).filter(e -> e.getAge() > 30)
        .collect(Collectors.toSet());
    System.out.println(result2);

}
  • map:接收一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:用于将多个流合并成一个流,俗称流的扁平化
@Test
void test8() {
    // 将集合中员工的名字组成一个新流
    Stream<String> stringStream = employees.stream().map(Employee::getName);
    stringStream.forEach(System.out::println);

    // 准备数据
    List<List<Employee>> emp = new ArrayList<>();
    emp.add(employees.subList(1, 3));
    emp.add(employees.subList(3, 6));
    emp.add(employees.subList(6, 9));
    emp.add(employees.subList(9, 12));
    // 将流中每个元素都生成一个新流,再将新流中的元素截取1个后,将这些流和并成一个
    List<Employee> employee = emp.stream()
        .flatMap(x -> x.stream().limit(1))
        .collect(Collectors.toList());
    employee.forEach(System.out::println);
}
  • sorted

使用流提供的sorted方法可以用于对集合中的元素进行排序操作,要注意的是,如果集合中的元素是自定义类,该类要实现Comparable接口。

Employee类实现一个Comparable接口,重写 compareTo 方法,设置自定义排序规则

@Data
@AllArgsConstructor
public class Employee implements Comparable<Employee>{
    private Long id;
    private String name;
    private Integer age;
    private Double salary;

    @Override
    public int compareTo(Employee target) {
        // 默认按年龄升序排序,如果年龄相同,按薪资降序排序
        Objects.requireNonNull(target);
        Objects.requireNonNull(target.getAge());
        Objects.requireNonNull(target.getSalary());
        if (!this.age.equals(target.getAge())) {
            return this.age - target.getAge();
        }
        else {
            return (int) (target.getSalary() - this.salary);
        }
    }
}

进行排序

List<Employee> employees = DataUtils.getEmployees();
@Test
void test6() {
    Stream<Employee> stream = employees.stream();
    List<Employee> result1 = stream.sorted().collect(Collectors.toList());
    System.out.println(result1);
}
  • sorted(Comparator<? super T> comparator)

自定义的类也可以不实现 Comparable 接口,可以使用流的 sorted(Comparator<? super T> comparator)方法,传入一个 Comparator 的 Lambda 表达式来进行排序

@Test
void test7() {
    Comparator<Employee> c1 = (e1,e2)-> e1.getAge() - e2.getAge();
    Comparator<Employee> c2 = c1.thenComparing((e1,e2)-> {
        Double d = e2.getSalary() - e1.getSalary();
        return d.intValue();
    });


    Stream<Employee> stream2 = employees.stream();
    List<Employee> result2 = stream2.sorted(c2).collect(Collectors.toList());
    System.out.println(result2);

    Stream<Employee> stream3 = employees.stream();
    // 先按年龄升序排序,年龄相同按薪资倒序
    List<Employee> result3 = stream3
        .sorted(Comparator.comparing(Employee::getAge)
                .thenComparing(Comparator.comparing(Employee::getSalary).reversed())
               ).collect(Collectors.toList());
    System.out.println(result3);
}

终端操作将生成最终结果。也就是及早求值

  • anyMatch:判断元素中是否有任意一个满足给定条件
  • allMatch:判断是否所有元素都满足给定条件
  • noneMatch:判断是否所有元素都不满足给定条件
@Test
void test9() {
    List<Employee> employees1 = DataUtils.getEmployees();
    // 是否有员工的年龄大于30
    boolean b = employees1.stream().anyMatch(employee -> employee.getAge() > 30);
    System.out.println("b = " + b);
    // 是否所有员工的年龄都大于30
    boolean b1 = employees1.stream().allMatch(employee -> employee.getAge() > 30);
    System.out.println("b1 = " + b1);
    // 是否所有员工的年龄都小于30
    boolean b2 = employees1.stream().noneMatch(employee -> employee.getAge() > 30);
    System.out.println("b2 = " + b2);
}
  • findFirst:查找第一个元素并返回
  • findAny:查找返回任意一个元素,如果数据有序,一般返回第一个,可以使用parallel()生成并行流增加随机性
@Test
void test10() {
    List<Employee> employees1 = DataUtils.getEmployees();
    Optional<Employee> first = employees1.stream().findFirst();
    if (first.isPresent()) {
        System.out.println(first.get());
    }

    Optional<Employee> any = employees.stream().parallel().findAny();
    if (any.isPresent()) {
        System.out.println(any.get());
    }

    List<Integer> list = Arrays.asList(1, 3, 5, 7);
    Optional<Integer> any1 = list.stream().parallel().findAny();
    System.out.println(any1.get());
}
  • reduce:将流中的元素归约成一个值
  • max:最大值
  • min:最小值
  • count:个数
@Test
void test11() {
    List<Employee> employees1 = DataUtils.getEmployees();
    // 求所有员工的年龄之和
    Optional<Integer> sum = employees1.stream().map(Employee::getAge).reduce((x1,x2)->x1+x2);
    sum.ifPresent(System.out::println);

    // 求员工的最大年龄
    Optional<Integer> max = employees1.stream().map(Employee::getAge).max((Integer::compareTo));
    max.ifPresent(System.out::println);

    // 求员工的最小年龄
    Optional<Integer> min = employees1.stream().map(Employee::getAge).min((x1, x2) -> x1 - x2);
    min.ifPresent(System.out::println);

    // 求员工总数
    long count = employees1.stream().count();
    System.out.println(count);
}
  • foreach
@Test
void test12() {
    // 遍历输出每一个元素
    List<Employee> employees1 = DataUtils.getEmployees();
    employees1.forEach(System.out::println);
}

如前面所讲的.collect(Collectors.toList()),我们在对流进行一系列操作过后将流收集生成一个结果。

下面我们将进一步了解collect的用法。

  • 生成List集合:Collectors.toList
  • 生成Set集合:Collectors.toSet
  • 生成Map集合:Collectors.toMap()
  • 生成指定类型的集合:Collectors.toCollection(xxx)
@Test
void test13() {
    List<Employee> employees1 = DataUtils.getEmployees();
    List<Employee> collect1 = employees1.stream().collect(Collectors.toList());

    Set<Employee> collect2 = employees1.stream().collect(Collectors.toSet());

    ArrayList<Employee> collect3 = employees1.stream().collect(Collectors.toCollection(ArrayList::new));

    Map<String, Integer> collect4 = employees1.stream().distinct().collect(Collectors.toMap(Employee::getName, Employee::getAge));

    System.out.println(collect4);
}
  • Collectors.counting():计算元素个数
  • Collectors.averagingDouble():计算平均数
  • Collectors.summingDouble(): 计算总合
  • Collectors.maxBy():计算最大值
  • Collectors.minBy():计算最小值
  • Collectors.summarizingDouble:一次性生成上述五种统计数据
@Test
void test14() {
    List<Employee> employees1 = DataUtils.getEmployees();
    Long count = employees1.stream().collect(Collectors.counting());
    System.out.println("count = " + count);
    Double avgAge = employees1.stream().collect(Collectors.averagingDouble(Employee::getAge));
    System.out.println("avgAge = " + avgAge);
    Double sumSalary = employees1.stream().collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println("sumSalary = " + sumSalary);
    Optional<Employee> maxAge = employees1.stream().collect(Collectors.maxBy((e1, e2) -> e1.getAge() - e2.getAge()));
    System.out.println("maxAge = " + maxAge);
    Optional<Employee> minAge = employees1.stream().collect(Collectors.minBy((e1, e2) -> e1.getAge() - e2.getAge()));
    System.out.println("minAge = " + minAge);
    DoubleSummaryStatistics collectSalary = employees1.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println("collectSalary = " + collectSalary);
}
  • Collectors.partitioningBy():根据某个判断规则将流中的数据分为两块
  • Collectors.groupingBy()
    • 传入一个参数,该结果返回一个MapMap的 Key 保存用来划分分组的值,Value 保存的是结果。
    • 传入两个参数,第一个参数是 Map 的 Key(根据哪种数据进行分组),第二个参数接收一个收集器
    • 传入三个参数,第一个参数是 Map 的 Key,第二个参数是生成的 Map 类型,第三个参数是收集器
@Test
void test15() {
    List<Employee> employees1 = DataUtils.getEmployees();

    // 以员工年龄是否大于30来将将员工分块
    Map<Boolean, List<Employee>> collect = employees1.stream().collect(Collectors.partitioningBy(employee -> employee.getAge() > 30));
    System.out.println("collect = " + collect);


    // 薪资相同的员工分到一组
    Map<Double, List<Employee>> collect1 = employees1.stream().collect(Collectors.groupingBy(Employee::getSalary));
    System.out.println("collect1 = " + collect1);

    // 按照工资高低分为高薪员工和低薪员工
    Map<String, Set<Employee>> collect2 = employees1.stream().collect(Collectors.groupingBy(emp -> emp.getSalary() > 10000 ? "高薪" : "低薪", Collectors.toSet()));
    System.out.println("collect2 = " + collect2);

    // 指定生成的map类型
    TreeMap<Double, Set<Employee>> collect3 = employees1.stream().collect(Collectors.groupingBy(Employee::getSalary, TreeMap::new, Collectors.toSet()));
    System.out.println("collect3 = " + collect3);
}
@Test
public void test22() {
    // 先按年龄分组,再按薪资高低分组
    Map<String, Map<String, List<Employee>>> collect = DataUtils.getEmployees().stream().collect(Collectors.groupingBy(
        e -> e.getAge() > 30 ? "大龄" : "正常",
        Collectors.groupingBy(e -> {
            if (e.getSalary() > 15000) {
                return "高薪";
            } else if (e.getSalary() > 10000) {
                return "中薪";
            } else {
                return "低薪";
            }
        })));
    System.out.println(collect);
}

对字符串流进行操作

  • Collectors.joining():对流中每一个对象应用toString方法得到的所有字符串连接成一个字符串
    • 没传任何参数,将所有数据直接拼接成一个字符串
    • 传入一个参数将设置数据之间的分隔符
    • 传入三个参数,将设置分隔符,字符串的前缀和后缀
@Test
void test16() {
    List<Employee> employees1 = DataUtils.getEmployees();
    // 设置分隔符,前缀和后缀
    String collect = employees1.stream().map(Employee::getName).collect(Collectors.joining(",","[","]"));
    System.out.println(collect);
}

输出结果:

[Morton,Morton,Dahlia,Babb,Rice,Handy,Rudy,Grady,Brenton,Vinson,Kemp,Sebastian,Evangeline,Lisette,Wilkes,Leach,Geiger,Holden,Thorpe,Adrienne,Calderon]

如将 Employee 的所有 age 属性映射成一个 List 集合,相当于 employees1.stream().map(Employee::getAge).collect(Collectors.toList());

@Test
void test16() {
    List<Employee> employees1 = DataUtils.getEmployees();

    List<Integer> collect1 = employees1.stream().collect(Collectors.mapping(Employee::getAge, Collectors.toList()));

    System.out.println(collect1);
}
  • Collectors.reducing()
    • 无初始值
    • 有初始值
    • 有初始值和转换函数
@Test
void test17() {
    List<Employee> employees1 = DataUtils.getEmployees();
    // 找到工资最高的员工
    Optional<Employee> collect = employees1.stream().collect(Collectors.reducing(
        (e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2
    ));
    System.out.println("collect = " + collect);

    // 新增一个员工,再求薪资最高的员工
    Employee e = new Employee(50L,"kkk",40,25000.0);
    Employee maxSalaryEmployee = employees1.stream().collect(
        Collectors.reducing(e, (e1, e2) -> e1.getSalary() > e2.getSalary() ? e1 : e2));
    System.out.println(maxSalaryEmployee);

    // 求出新员工和之前员工的总薪资
    Double sum = employees1.stream().collect(Collectors.reducing(e.getSalary(), Employee::getSalary, Double::sum));
    System.out.println(sum);
}
  • of(int value)of(int ...values): 生成一个或多个数据的流
  • range(int startInclusive, int endExclusive):左闭右开区间的流
  • rangeClosed(int startInclusive, int endExclusive):左闭右闭区间的流
  • mapToInt:将包装类型(如 Integer)的流转换成基本类型(int)的流
@Test
void test18() {
    IntStream intStream = IntStream.of(1, 2, 3);
    intStream.forEach(System.out::println);
    System.out.println("=====================");

    IntStream range = IntStream.range(1, 10);
    range.forEach(System.out::println);
    System.out.println("=====================");

    IntStream rangeClosed = IntStream.rangeClosed(1, 10);
    rangeClosed.forEach(System.out::println);
    System.out.println("=====================");

    DoubleStream doubleStream = employees.stream().mapToDouble(Employee::getSalary);
    doubleStream.forEach(System.out::println);
}
  • summaryStatistics(): 获取流中元素的各种统计结果
  • count():获取流中元素个数
  • sum():计算流中元素的总和
  • average():计算流元素的平均值
  • min():计算流中元素的最小值。
  • max:计算流中元素的最大值。
  • boxed():转换成对应的包装类型的流
    • IntStream是存的是 int 类型的 stream,而Steam<Integer>是一个存了 Integer 的 stream。
    • boxed 的作用就是将int类型的stream转成了Integer类型的 Stream,mapToInt作用刚好相反
@Test
void test19() {
    IntSummaryStatistics intSummaryStatistics = IntStream.rangeClosed(1, 10).summaryStatistics();
    System.out.println(intSummaryStatistics);

    System.out.println(IntStream.rangeClosed(1, 10).min());
    System.out.println(IntStream.rangeClosed(1, 10).max());
    System.out.println(IntStream.rangeClosed(1, 10).sum());
    System.out.println(IntStream.rangeClosed(1, 10).average());
    System.out.println(IntStream.rangeClosed(1, 10).count());

    Stream<Integer> boxed1 = IntStream.rangeClosed(1, 10).boxed();
    IntStream intStream = boxed1.mapToInt(i -> i);
    intStream.forEach(System.out::println);

}

串行流转化为并行流有两种方式

  • 如果我们已经有一个串行流,我们要从这个串行流中获取并行流,我们只需要调用parallel()方法
  • 如果从一个集合中直接获取并行流,可以调用parallelStream()方法
@Test
void test20() {
    List<Employee> employees1 = DataUtils.getEmployees();
    // 生成并行流的方式1
    employees1.parallelStream().forEach(System.out::println);
    System.out.println("====================================");
    // 生成并行流的方式2
    employees1.stream().parallel().forEach(System.out::println);
}

注意:并行流操作共享变量,会影响计算结果

@Test
public void test30() {
    A a = new A();
    LongStream.range(1, 1000000000).parallel().forEach(x -> a.add(x));
    // 每次结果都不一样
    // 这里是三次运行结果
    //64272699449426073
    //67894274051000101
    //80839784281725189
    System.out.println(a.i);
}

class A {
    public long i = 0;
    public void add(long j) {
        i += j;
    }
}

使用并行流应该考虑以下几点:

  • 留意拆装箱成本
  • 流中依赖于元素顺序的操作,在并行流上执行的代价非常大;
  • 考虑流的流水线操作总成本,对于较小的数据量,并不适合使用并行流;
  • 考虑流背后的数据结构是否易于分解,不易分解的数据结构不适合使用并行流。

of方法接收指定的值用于创建,parse用于指定格式类型的值创建,now根据当前日期创建

@Test
public void test() {
    // 创建日期,指定年月日
    LocalDate date1 = LocalDate.of(2023, 3, 24);
    LocalDate date2 = LocalDate.of(2023, Month.MARCH, 24);
    System.out.println(date1);
    System.out.println(date2);

    // 通过字符串来创建一个LocalDate
    LocalDate parse = LocalDate.parse("2023-03-24");
    // 解析自定义的格式
    LocalDate parse1 = LocalDate.parse("2023/03/24", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    System.out.println(parse);
    System.out.println(parse1);

    // 获取当前时间
    LocalDate now = LocalDate.now(); // 系统默认时区
    LocalDate now1 = LocalDate.now(Clock.systemDefaultZone()); // 系统默认时区
    LocalDate now2 = LocalDate.now(ZoneId.of("Asia/Shanghai")); // 指定时区
    System.out.println(now);
    System.out.println(now1);
    System.out.println(now2);

    // 指定一年中的第几天
    LocalDate date = LocalDate.ofYearDay(2023, 15);
    System.out.println(date);
}
@Test
void test1() {
    // 创建时间,指定时、分、秒、纳秒 用于创建,of方法有多个重载
    LocalTime of = LocalTime.of(0, 1, 2, 3);
    // 创建时间,指定格式时间
	// 通过字符串来创建一个LocalTime
    LocalTime p = LocalTime.parse("20:20:20"); // 默认格式
    LocalTime p1 = LocalTime.parse("10/20/35", DateTimeFormatter.ofPattern("HH/mm/ss"));// 指定格式
    System.out.println(p);
    System.out.println(p1);
    // 获取当前时间
    LocalTime now = LocalTime.now(); // 系统默认时区
    LocalTime.now(ZoneId.of("Asia/Shanghai")); // 指定时区
    LocalTime.now(Clock.systemDefaultZone()); // 系统默认时区
    System.out.println(now);
    // 一天中的时间戳获取
    LocalTime localTime = LocalTime.ofSecondOfDay(1 * 24 * 60 * 60 - 1); // 当天第多少秒
    LocalTime localTime1 = LocalTime.ofNanoOfDay(1L * 60 * 60 * 1000_000_000); // 当天第多少纳秒
    System.out.println(localTime);
    System.out.println(localTime1);
}
@Test
public void test03() {
    LocalDate nowDate = LocalDate.now();
    LocalTime nowTime = LocalTime.now();
    // of方法有多个构造器
    LocalDateTime of = LocalDateTime.of(nowDate, nowTime); // LocalDate LocalTime
    LocalDateTime.of(2010, 10, 20, 17, 50); // 年 月 日  时 分,
    // 按格式创建
    // 通过字符串来创建LocalDateTime
    LocalDateTime parse = LocalDateTime.parse("2020-01-10T18:01:50.722"); // 默认格式
    // 按照自己传入的时间日期格式进行解析
    LocalDateTime parse1 = LocalDateTime.parse("2020-01-20 20:20:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));	// 自定义格式
    // 获取当前
    LocalDateTime now = LocalDateTime.now(); // 系统默认时区
    LocalDateTime.now(ZoneId.of("Asia/Shanghai")); // 指定时区
    LocalDateTime.now(Clock.systemDefaultZone()); // 系统默认时区
    // 时间戳秒、毫秒、时区
    LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(1578972303, 621000000, ZoneOffset.ofHours(8));
}
@Test
void test3() {
    LocalDate date = LocalDate.now();
    System.out.println(date.getYear());

    // 获取当前月份对应的英文
    System.out.println(date.getMonth());

    // 获取当前月份的数值
    System.out.println(date.getMonthValue());
    System.out.println(date.getDayOfMonth());
    System.out.println(date.getDayOfWeek());
    System.out.println(date.getDayOfYear());

    // 根据指定的单位来获取数据,比如一周中的第几天、一个月中的第几天、一年中的第几天
    System.out.println(date.get(ChronoField.DAY_OF_WEEK));
    System.out.println(date.get(ChronoField.DAY_OF_MONTH));
    System.out.println(date.get(ChronoField.DAY_OF_YEAR));

    // 是否支持该字段查询
    System.out.println(date.isSupported(ChronoField.HOUR_OF_DAY));

    // 获取该字段的范围
    System.out.println(date.range(ChronoField.DAY_OF_WEEK));
    System.out.println(date.range(ChronoField.DAY_OF_MONTH));
    System.out.println(date.range(ChronoField.DAY_OF_YEAR));

    // 这个月有多少天
    System.out.println(date.lengthOfMonth());
    // 今年有多少天
    System.out.println(date.lengthOfYear());
    // 是否为闰年
    System.out.println(date.isLeapYear());
}
@Test
void test5() {
    LocalTime time = LocalTime.now();

    System.out.println(time.getHour());
    System.out.println(time.getMinute());
    System.out.println(time.getSecond());

    System.out.println(time.isSupported(ChronoField.DAY_OF_MONTH));

    System.out.println(time.range(ChronoField.HOUR_OF_DAY));
    System.out.println(time.range(ChronoField.MINUTE_OF_DAY));
    System.out.println(time.range(ChronoField.NANO_OF_DAY));

    // 当天时间戳: 纳秒
    System.out.println(time.toNanoOfDay());
    // 当天时间戳: 秒
    System.out.println(time.toSecondOfDay());
}

日期时间综合和了日期时间的所有方法,即可以用于获取时间相关数据,也可以获取日期相关数据。

@Test
void test4() {
    LocalDateTime dateTime = LocalDateTime.now();
    // 获取日期时间的各种数值
    System.out.println(dateTime.getYear());
    System.out.println(dateTime.getMonthValue());
    System.out.println(dateTime.getDayOfMonth());
    System.out.println(dateTime.getHour());
    System.out.println(dateTime.getMinute());
    System.out.println(dateTime.getSecond());

    // 是否支持该字段
    System.out.println(dateTime.isSupported(ChronoField.NANO_OF_SECOND));

    // 获取字段的范围
    System.out.println(dateTime.range(ChronoField.HOUR_OF_DAY));
    System.out.println(dateTime.range(ChronoField.DAY_OF_WEEK));
    System.out.println(dateTime.range(ChronoField.DAY_OF_YEAR));

    // 获取字段的具体值
    System.out.println(dateTime.get(ChronoField.HOUR_OF_DAY));
    System.out.println(dateTime.get(ChronoField.DAY_OF_WEEK));
    System.out.println(dateTime.get(ChronoField.DAY_OF_YEAR));
}
@Test
void test6() {
    // 指定格式
    DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
    System.out.println(LocalDateTime.now().format(formatter1));

    // 指定格式,指定地区
    DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH-mm-ss", Locale.CHINA);
    System.out.println(LocalDateTime.now().format(formatter2)); // 格式化为指定字符串

    // 用于LocalDate 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为23-3-24 即 2023-03-24
    DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
    System.out.println(LocalDate.now().format(formatter3));
    System.out.println(LocalDateTime.now().format(formatter3));


    // 用于LocalTime 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为 下午3:06 即 15:06
    DateTimeFormatter formatter4 = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
    System.out.println(LocalTime.now().format(formatter4));
    System.out.println(LocalDateTime.now().format(formatter4));


    // 用于LocalDateTime 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为 23-3-24 下午3:08 即 2023-03-24 15:06
    DateTimeFormatter formatter5 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
    System.out.println(LocalDateTime.now().format(formatter5));
}
@Test
void  test7() {
    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

    DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

    // 日期格式化字符串
    String format = LocalDate.now().format(dateFormatter);
    // 时间格式化字符串
    String format1 = LocalTime.now().format(timeFormatter);
    // 日期时间格式化字符串
    String format2 = LocalDateTime.now().format(formatter);

    // 格式化生成日期或时间
    LocalDate parse = LocalDate.parse("2020/01/20", dateFormatter);
    LocalTime parse1 = LocalTime.parse("20:20:10", timeFormatter);
    LocalDateTime parse2 = LocalDateTime.parse("2020/01/20 20:10:02", formatter);
}
@Test
void test8() {
    LocalDate now = LocalDate.now();
    // 修改Month为2月
    LocalDate date = now.withMonth(2);
    // 修改Year
    LocalDate date1 = now.withYear(2018);
    // 修改 天在月中为20号
    LocalDate date2 = now.withDayOfMonth(20);
    // 修改天在年中为200天
    LocalDate date3 = now.withDayOfYear(200);
    // 修改为3月,指定单位修改
    LocalDate with = now.with(ChronoField.MONTH_OF_YEAR, 10);
}
@Test
void test9() {
    LocalTime now = LocalTime.now();
    // 纳秒改为1
    LocalTime localTime = now.withNano(1);
    // 秒改为30
    LocalTime localTime1 = now.withSecond(30);
    // 分钟改为20
    LocalTime localTime2 = now.withMinute(20);
    // 小时改为12
    LocalTime localTime3 = now.withHour(12);
    // 小时改为20, 指定单位修改
    LocalTime with = now.with(ChronoField.HOUR_OF_DAY, 20);
}
@Test
void test9() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("now.withYear(2024) = " + now.withYear(2024));
    System.out.println("now.withMonth(2) = " + now.withMonth(2));
    System.out.println("now.withDayOfMonth(20) = " + now.withDayOfMonth(20));
    System.out.println("now.withHour(10) = " + now.withHour(10));
    System.out.println("now.withMinute(20) = " + now.withMinute(20));
    System.out.println("now.withSecond(40) = " + now.withSecond(40));
    System.out.println(now.with(ChronoField.HOUR_OF_DAY, 13));
}

DateTime为例

@Test
void test9() {
    LocalDateTime now = LocalDateTime.now();

    // 减1天
    LocalDateTime dateTime = now.minusDays(1);
    // 减2个星期
    LocalDateTime dateTime1 = now.minusWeeks(2);
    // 减3个月
    LocalDateTime dateTime2 = now.minusMonths(3);
    // 减1年
    LocalDateTime dateTime3 = now.minusYears(1);
    // 减2个小时
    LocalDateTime dateTime4 = now.minusHours(2);
    // 减20分钟
    LocalDateTime dateTime5 = now.minusMinutes(20);
    // 减2天,自定义单位
    LocalDateTime minus = now.minus(2, ChronoUnit.DAYS);

    // 加20天
    LocalDateTime dateTime6 = now.plusDays(20);
    // 加2周
    LocalDateTime dateTime7 = now.plusWeeks(2);
    // 加2个月
    LocalDateTime dateTime8 = now.plusMonths(2);
    // 加1年
    LocalDateTime dateTime9 = now.plusYears(1);
    // 加2小时
    LocalDateTime dateTime10 = now.plusHours(2);
    // 加10分
    LocalDateTime dateTime11 = now.plusMinutes(10);
    // 加20秒
    LocalDateTime dateTime12 = now.plusSeconds(20);
    // 加2000纳秒
    LocalDateTime dateTime13 = now.plusNanos(2000);
    // 加10天,自定义单位
    LocalDateTime plus = now.plus(10, ChronoUnit.DAYS);
}
  • a.isBefore(b):时间日期 a 是否早于 b
  • a.isAfter(b):时间日期 a 是否晚于 b
  • a.isEqual(b):时间日期 a 是否和 b 相同

以上api同时适用于LocalDateTime,LocalTimeLocalDate

@Test
void test10() {
    LocalDate date1 = LocalDate.now();
    LocalDate date2 = LocalDate.parse("2020-01-20");
    // date1是否在date2之前
    boolean before = date1.isBefore(date2);
    // date1是否在date2之后
    boolean after = date1.isAfter(date2);
    // date1是否和date2相等
    boolean equal = date1.isEqual(date2);
}
@Test
public void test11() {
    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    // 用 LocalDate 和 LocalTime 生成 LocalDateTime
    LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);

    // LocalDateTime 转换成 LocalDate
    LocalDate date = localDateTime.toLocalDate();
    // LocalDateTime 转换成 LocalTime
    LocalTime localTime1 = localDateTime.toLocalTime();

    // LocalDate 加上 LocalTime 得到 LocalDateTime  时、分、秒、纳秒
    LocalDateTime dateTime = localDate.atTime(2, 30, 20, 1000);
    LocalDateTime dateTime1 = localDate.atTime(localTime);

    // 获取一天开始的时间即2023-03-24T00:00,LocalDate 转成 LocalDateTime
    LocalDateTime dateTime2 = localDate.atStartOfDay();

    // LocalTime 加上 LocalDate 得到 LocalDateTime
    LocalDateTime dateTime3 = localTime.atDate(localDate);
}

时间戳是指格林威治时间自 1970 年 1 月 1 日(00:00:00 GMT)至当前时间的总秒数

@Test
public void test12() {
    // 当前时间
    Instant now = Instant.now();
    // 默认格式解析生成
    Instant parse = Instant.parse("2020-01-11T02:02:53.241Z");
    // 时间戳 毫秒生成
    Instant instant = Instant.ofEpochMilli(90 * 60 * 1000);
    // 时间戳 毫秒 + 纳秒
    Instant instant1 = Instant.ofEpochSecond(60 * 60, 30L * 60 * 1000_000_000);
    // Unix 元年时间 1970-01-01 00:00:00
    Instant instant2 = Instant.EPOCH;
}

和之前的时间、日期、日期时间相同也有获取值、修改、比较等操作,这里不再详述。

Duration可以用于获取两个相同类型时间或日期的时间段,或指定特定类型的时间段。如果第一个时间比第二个时间大,获取的值为负数。 Duration同样支持计算和修改值,同时还增加了乘除操作

@Test
public void test12() {
    // 修改时区为东八区
    Instant now = Instant.now(Clock.offset(Clock.systemUTC(),Duration.ofHours(8)));
    // 创建Duration
    Instant parse = Instant.parse("2022-03-24T02:02:53.241Z");
    Duration between = Duration.between(now,parse).abs();
    Duration duration1 = Duration.ofDays(1);
    Duration duration = Duration.ofHours(1);
    Duration duration2 = Duration.ofMinutes(1);
    Duration duration3 = Duration.ofSeconds(1);
    Duration duration4 = Duration.ofSeconds(1, 1000_000_000);
    Duration duration5 = Duration.ofMillis(10000);
    Duration duration6 = Duration.ofNanos(10000000);
    Duration duration10 = Duration.of(3, ChronoUnit.MINUTES);

    // 获取绝对值
    Duration abs = duration.abs();
    // 获取秒数
    long l = duration.get(ChronoUnit.SECONDS);
    // 获取毫秒数
    int nano = duration.getNano();
    // 获取秒数
    long seconds = duration.getSeconds();

    // 获取支持的 TemporalUnit值
    List<TemporalUnit> units = duration.getUnits();

    // 舍去小数部分
    // 转换成天数
    long l1 = between.toDays();
    // 转换成小时数
    long l2 = between.toHours();
    // 转换成分钟数
    long l3 = between.toMinutes();
    // 转换成秒数
    long l4 = between.toMillis();
    // 转换成纳秒数
    long l5 = between.toNanos();

    // 乘除运算
    Duration duration7 = Duration.ofHours(10);
    Duration duration8 = duration7.dividedBy(10);
    Duration duration9 = duration7.multipliedBy(10);

    // 时间运算
    // 当前时间减去3个小时
    System.out.println(now.minus(Duration.ofHours(3)));
}

相关文章