Java 8 七大新特性实践

目录

一、Lambda 表达式

1)业务场景

2)普通方法

3)接口

4)匿名内部类

5)Lambda 表达式

二、函数式接口

1)Predicate

2)Consumer

3)Function

4)Supplier

三、接口改动

四、Optional

场景一:新客户处理

场景二:判断 DTO 的 errCode 属性

场景三:根据 DTO 的 errCode 做对应的处理

小结

五、方法引用

六、Stream API

组成

集合转换

求最大/小值、求和、求均值

七、新的时间类

优点

LocalDateTime、LocalDate、LocalTime

日期计算

日期解析和格式化


一、Lambda 表达式

1)业务场景

假设我们现在有一个业务场景,需要从一个 List 集合中查询符合条件的客户出来,并将对应的客户进行相应的业务处理。人的抽象我们用一个 Person 类表示,如下:

public class Person {

	private String name;
	private Integer age;
	private Sex gender;
	private String emailAddress;

	public void printPerson() {

		// ...

	}

	public enum Sex {
		MALE, FEMALE
	}

	// getter、setter method...

}

Person 为客户基础抽象类,随着系统的逐步升级,Person 会被赋予更多的客户属性,而相应的条件查询筛选也会随着变化,下面将使用传统的普通方法、接口、匿名内部类和 Lambda 表达式四种方式,逐步递进来表现 Lambda 表达式的魅力。

2)普通方法

最传统粗暴的方式,就是给每一种不同的条件查找单独写一个方法来处理,例如要获取所有年龄大于 age 的客户,代码如下:

	/**
	 * 处理所有年龄大于 age 的 Person
	 */
	public static void printPersonsOlderThan(List<Person> roster, int age) {
		for (Person p : roster) {
			if (p.getAge() >= age) {
				p.printPerson();
			}
		}
	}

如果现在需求变更,需要处理某一个年龄范围的客户,那就需要新增一个方法,比如:

	/**
	 * 处理所有年龄在 [low, high] 的 Person 
	 */
	public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high) {
		for (Person p : roster) {
			if (low <= p.getAge() && p.getAge() <= high) {
				p.printPerson();
			}
		}
	}

没错,也就是每次查询需求有一点小变化,我们都要为此编写一个新的方法来处理,但除了 if 条件逻辑语句之外的大部分代码都是重复的,所以这种方式会导致系统存在大量的冗余代码。

3)接口

为了解决传统的普通方法导致的大量冗余代码问题,其实我们已经发现,每次条件的变化影响的都是 if 的逻辑语句,那么只需要把经常变化的逻辑语句抽象成一个 CheckPerson 接口,后续的业务变化就交给具体的实现类来处理,就可以防止频繁改动方法的源代码,复用现有的查找处理方法。

CheckPerson 接口

public interface CheckPerson {

	boolean test(Person p);

}

CheckPersonEligibleForSelectiveService 实现类

public class CheckPersonEligibleForSelectiveService implements CheckPerson {

	@Override
	public boolean test(Person p) {
		return p.getGender() == Person.Sex.MALE
				&& p.getAge() >= 18
				&& p.getAge() <= 25;
	}

}

 print 方法

	public static void printPersons(List<Person> roster, CheckPerson tester) {
		for (Person p : roster) {
			if (tester.test(p)) {
				p.printPerson();
			}
		}
	}

最后获取合适的客户只需要传递具体的实现类,后续修改也可以不改动方法源代码,如下:

	printPersons(roster, new CheckPersonEligibleForSelectiveService());

这种方式比起传统方法可以不修改源代码,但是如果业务要筛选的条件非常多,就需要很多的实现类,但这些类往往只是一次性的,用过之后就很少再使用,所以也是一种浪费。

4)匿名内部类

接口实现类的方式解决了方法源代码频繁改动冗余的问题,但会增加很多类,为了进一步优化,可以使用匿名内部类,直接实现接口并当做参数传入方法中使用,最终代码的优化工作又更进了一步。

	printPersons(roster, new CheckPerson() {
		public boolean test(Person p) {
			return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25;
		}
	});

5)Lambda 表达式

尽管匿名内部类的方式已经很优雅,节省了大量冗余代码,提高了代码的扩展性和复用性。但还存在着些许冗余信息,比如 CheckPerson 接口的 new 创建语句,接口的 test 方法,我们希望只关注不同点,也就是指具体的方法体,而忽略接口名和接口方法这些相同点,这时 Lambda 表达式语法应运而生,它完成了我们的期望,匿名内部类中的写法用 Lambda 表达式就变成下面这样:

	printPersons(roster,
			p -> p.getGender() == Person.Sex.MALE
					&& p.getAge() >= 18
					&& p.getAge() <= 25
	);

最终的代码变得非常的简洁和优雅,只保留了我们需要专心关注的方法体信息。

Lambda 表达式与匿名内部类相似,你可以认为它是一种匿名方法,因为它省略了方法名、返回值和参数等信息,会根绝类型自行推断最优的方法调用。

Lambda 表达式的特征

  1. 参数列表,即上述方法中的 “p”,如果有多个,则以逗号分割,并且用“() ”包括起来,比如 (p1, p2);
  2. 箭头,即“->”符号,表示方法体的开始;
  3. 方法体,如果方法体只有一行代码,可以不加中括号"{}"和分号。如果有多行,则需要加中括号"{}"和分号,比如:{ String p1 = 1; return p1 + "2"; }。

二、函数式接口

函数式接口即指接口中有且仅有一个抽象方法,这样的接口称为函数式接口,你也可以使用 @FunctionInterface 标记当前接口为函数式接口,限制当前接口只能有一个抽象方法,如果有多个抽象方法就会报错。

Lambda 表达式使用的基础其实就是基于函数式接口,只有是含有单一抽象方法的函数式接口,才可以使用 Lambda 表达式的语法糖。

Java 8 的 JDK 中也自带有很多的现成的函数式接口,常用的比如 Predicate、Consumer、Function 和 Supplier,这些函数式接口都放在 java.util.function 包下。

1)Predicate

在上一章的 Lambda 表达式案例中,我们使用到了 CheckPerson 接口的 boolean test(Person person) 方法来抽象不同业务场景下的 Person 筛选条件,其实在 Java 8 中,有自带 boolean test(T t) 方法的函数式接口 Predicate。

你可以把 Predicate 理解为对任意一个对象进行逻辑判断,最终返回 Boolean 结果的一个抽象接口,基于 Predicate 可以继续优化上一章案例的 printPersons 方法,而方法的最终使用方法与 CheckPerson 接口的方式相同,只不过连接口的定义这一步都节省了。

print 方法

	public static void printPersons(List<Person> roster, Predicate<Person> predicate) {
		for (Person p : roster) {
			if (predicate.test(p)) {
				p.printPerson();
			}
		}
	}

调用方式与原来的相同,如下:

	printPersons(roster,
			p -> p.getGender() == Person.Sex.MALE
					&& p.getAge() >= 18
					&& p.getAge() <= 25
	);

大部分情况下,Predicate 接口的 test() 方法就能抽象大部分的逻辑表达式语句,如果需要更细粒度的逻辑划分,可以使用 Predicate 接口的默认方法和静态方法来组合。

Predicate 接口的默认和静态方法

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    // and 
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    // or
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    // ! 取反
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    // 通过两个对象的 equals 方法来对比
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

2)Consumer

经过我们的 Lambda 表达式和 Predicate 函数式接口优化后,我们对 Person 目标客户群的筛选处理方法已经很优雅了。但业务需求总是多变复杂的,现在我们希望对筛选后的 Person 客户做多样的处理,那么与筛选条件类似,需要把处理的方法也抽象成一个接口,那就可以使用 JDK 8 自带的 Consumer 接口的 accept(T t) 方法,如下:

	public static void processPersons(List<Person> roster, Predicate<Person> predicate, Consumer<Person> consumer) {
		for (Person p : roster) {
			if (predicate.test(p)) {
				consumer.accept(p);
			}
		}
	}

方法的调用方式

	processPersons(roster,
			p -> p.getGender() == Person.Sex.MALE
					&& p.getAge() >= 18
					&& p.getAge() <= 25,
			Person::printPerson
	);

我们只需要将不同的处理方法同 Lambda 的写法传递给方法,可以不修改源代码。

Consumer 接口可以表示接受一个对象,并对对象进行处理,Consumer 的抽象方法和默认方法如下:

@FunctionalInterface
public interface Consumer<T> {

    // 处理对象 t
    void accept(T t);

    // 可以组成消费处理链 c.andThen(...).accpet(..)  
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

所以当我们需要应付对某个对象多变的处理需求时,就可以通过 Consumer 来抽象处理的方法,并通过 andThen 方法来自由组成处理的先后顺序。

3)Function

Function 接口如字面意思,代表一个接受类型 T 的参数并返回一个为非 void 的值方法的抽象,它提供了比 Predicate 和 Consumer 接口更加宽泛的意义。

Function 接口方法

@FunctionalInterface
public interface Function<T, R> {

    // 调用方法,接受 T 类型参数,返回 R 类型返回值
    R apply(T t);

    // 合并两个方法,传递前置处理方法
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    // 形成方法调用链,传递后置处理方法
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    // 返回原方法
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

在日常开发中我们经常会获取某个对象的字段值,但这个对象可能会有 NPE 问题,我们需要避免发生 NPE,而且对象为 Null 时就设置字段为一个默认值。

那么我们可以借鉴一下 python 语言中的 dict.get(obj, filedName, defaultValue) 方法,写一个 ObjectUtil 的 safeGet 方法,如下:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtil {

    public static <T, R> R safeGet(T obj, Function<T, R> getter, R defaultValue) {
        if (obj == null) {
            return defaultValue;
        }

        return getter.apply(obj);
    }

    public static void main(String[] args) {
        Person person = null;

        Integer age = safeGet(person, Person::getAge, 0);
        // 相当于
//		Integer age = 0;
//		if (person != null) {
//			age = person.getAge();
//		}
    }
}

对于对象 NPE 的优雅处理,Java 8 中还可以使用 Optional,这个放到第四章再介绍。

4)Supplier

Supplier 接口为获取某类型对象的方法的抽象,接口中就只有一个 T get() 方法,比较简单,Supplier 主要是用来结合 Optional 和 Stream API 使用,等到第四章再做演示。

Supplier 接口方法

@FunctionalInterface
public interface Supplier<T> {

    T get();

}

三、接口改动

在第二章中,我们看到的几个函数式接口中,有 default 默认方法和 static 静态方法的身影,其实这也是 Java 8 中接口的一个新特性。

Predicate 类回顾

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    // 默认方法
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    // 默认方法
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    // 默认方法
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    // 静态方法
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Java 8 以前的接口只能定义抽象方法和全局静态常量,而 Java 8 中的接口增加了 defaultstatic 方法的定义,缩小了接口与抽象类的差距,使接口的使用场景更多,毕竟在 Java 中类只能继承一个父类,但可以实现多个接口,所以接口的使用会更加的灵活。

四、Optional

Optional 是 Java 8 中用来优雅处理对象 Null 值判断,避免 NPE 的一个新类,合理的利用 Optional 可以使你的代码更加简洁,并且不至于因为判断而导致 if 多层嵌套。下面将通过几个实例来展示合理使用 Optional 带来的魅力,并与 if 语句实现的代码做对比。

场景一:新客户处理

假设我们的业务系统在客户登录时,需要通过客户名获取客户的信息 Person,如果客户为空,则为新顾客,需要为其创建新数据,如果不为空直接获取信息返回,这是很常见的业务场景。通过 Optional 的 ofElseGet 方法来实现,只需要一行代码。

	public static void ofElseGet(Person exist) {
		// 不为空直接获取,为空创建新的 Person 数据
                Person p = Optional.ofNullable(exist).orElseGet(Person::new);
		// 相当于下面 6 行的效果
//              Person p;
//		if (exist!= null) {
//			p = exist;
//		} else {
//			p = new Person();
//		}
	}

Optional 的 orElseGet 方法,其实就是通过第三章中的函数式接口 Supplier 来实现,如下:

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

Supplier 接口抽象了对象的获取和创建方式,交由使用者自己定制。上面的例子在实际使用中,我们也不会直接只是调用对象构造器就完成创建,更好的方法其实是结合 Builder 设计模式来创建,如下:

	public static void ofElseGetWithBuilder(Person exist) {
		Person p = Optional.ofNullable(exist).orElseGet(
				() -> Person.builder().age(1)
						.emailAddress("address")
						.name("shq")
						.gender(Person.Sex.MALE)
						.build());
	}

通过 Optional 结合 Builder 可以让代码看起来更连贯,也更加简洁,关于 Builder 的使用,可以搜索一下 lombok。

场景二:判断 DTO 的 errCode 属性

很多时候,我们还要获取一个对象的属性值,并做后续的处理,比如很多第三方服务接口返回的 DTO 数据的字段,有可能是 Null 值,常见的比如 errCode 和 errMsg。

这种情况可以通过 Optional 的 map 方法获取对应的属性字段,如下:

    static class Result {
		private Integer errCode;
		private String errMsg;
		private String data;

                public Result() {
                }  
                
                public Result(Integer errCode, String errMsg, String data) {
                    // ....
                }               

                // getter、setter method...
	}

    public static void map(Result res) {
		Integer errCode = Optional.ofNullable(res).map(Result::getErrCode).orElse(0);
		// 相当于下面 6 行的效果
//              Integer errCode;
//		if (res != null) {
//			errCode  = res.getErrCode() == null ? res.getErrCode() : 0;
//		} else {
//			errCode  = 0;
//		}
	}

map 方法其实是通过 Function 函数式接口来完成这一处理,可以自己查看 Optional 的源码做深入的理解。

场景三:根据 DTO 的 errCode 做对应的处理

上个例子中,我们获取了 DTO 类的 errCode 之后,其实还需要判断 errCode 对应的值,再做对应的判断,比如 errCode 为 0 或者 null 时,一般都代表着接口调用成功,而 errCode 不为 null 并且值为非 0 通常代表着调用失败,我们经常需要通过判断 errCode 值来做对应的处理。这种情况可以结合 Optional 的 filter 和 ifPersent 方法,通过 filter 判断 DTO 的字段值,再通过 ifPersent 做相应的后续处理,比如调用成功时,输出 data 的值,调用失败时,输出 errMsg 的值。

具体代码如下:

	private static final Integer SUCCESS = 0;

    public static void consumeByErrCode(Result result) {
		Optional<Result> resultOpt = Optional.ofNullable(result);

		// 给太长的逻辑表达式取一个代表含义的名字,提高代码可读性
		Predicate<Result> successPre = r -> SUCCESS.equals(resultOpt.map(Result::getErrCode).orElse(0));
                // 调用成功
		resultOpt.filter(successPre)
				.ifPresent(r -> System.out.println(r.getData()));
		
		// 调用失败
		resultOpt.filter(successPre.negate())
				.ifPresent(r -> System.out.println(r.getErrMsg()));
	}

小结

通过上述几个例子,我们可以看到合理使用 Optional 并结合几个内置函数式接口,可以大大的避免 if (obj != null) 这类的逻辑判断出现,使代码看起来更加专注于业务的处理细节,推荐多利用 Optional 来处理对象的 NPE 问题。

不过在使用 Optional 的 filter 做对象状态属性这类判断需要逻辑上的一些转变,可能不如 if 语句那么直接,大家可以基于团队的习惯灵活使用,自己做取舍。

五、方法引用

方法引用其实在前面几张已经出现过数次了,就比如上一章中的 Result::getErrCode 其实就是方法引用。方法引用其实就是在 Lambda 表达式的基础上,进一步简化其代码的方式,比如把一个已存在的类或实例的方法当做函数式接口方法的入参时,就可以使用方法引用。

方法引用一共有 4 种:

  1. 静态方法引用,ContainingClass::staticMethodName
  2. 实例对象方法引用,containingObject::instanceMethodName
  3. 任意对象特定类型的实例方法引用,ContainingType::methodName
  4. 构造器引用,ClassName::new;

方法引用的特征就是“::”,左边是类名或者实例对象名,右边是方法名或者 new。

六、Stream API

Stream API 是 Java 8 中给 Collection 类新增的一种类 SQL 的集合操作方式。集合是我们平时用得很多的类,Collection 中例如 List、Set 和 Queue,在新增了 Stream API 之后,我们可以很方便的对这些集合类进行查找、排序、求和、获取最大/小值、转成 Map 和 Set 等这些操作,Stream API 封装了大量此类业务经常要使用到的方法,节省了我们重复封装编程的工作。

下面将通过几个案例来介绍常用的 Stream API,Stream API 对应 java.util.stream.Stream 这个接口,其他的方法用途就根据例子举一反三,选择合适的场合使用。

组成

一个 Stream API 的使用,通过是通过 Collection 接口的 stream 方法开始,然后中间使用 Stream API 提供的各种过滤、排序、映射、去重等处理组合,处理顺序根据具体业务组成,最后再通过 collect 方法和 Collector 类返回一个新的集合类,或者通过 findFirst、min、max 等求得最小/大值。

例如:

	public static void component(Collection<Person> personCollection) {
		// 开始:获取 Stream API 
		Stream<Person> stream = personCollection.stream();

		// 中间处理:查找 Person 为 Male
		Stream<Person> filterHandlerStream = stream.filter(p -> Person.Sex.MALE == p.getGender());

		// 结尾:获取过滤后的 List 结果
		List<Person> result = filterHandlerStream.collect(Collectors.toList());
	}

集合转换

    public static void collectAndCollector(Collection<Person> personCollection) {
		// collect(Collectors.toSet())
		// 获取 Person 名字的 set 集合
		Set<String> names = personCollection.stream().map(Person::getName)
				.collect(Collectors.toSet());

		// collect(Collectors.toMap(Person::getName, v -> v, (k1, k2) -> k1))
		// 以 Person 名字为 KEY,Person 为 VALUE 返回一个 Map
		Map<String, Person> namePersonMap = personCollection.stream()
				.collect(Collectors.toMap(Person::getName, v -> v, (k1, k2) -> k1));

		// collect(Collectors.groupingBy(Person::getName))
		// 以 Person 名字为 KEY 进行分组,把相同 name 的 Person 放在同一个 List 作为一个 VALUE, 返回一个 Map
		Map<String, List<Person>> namePersonListMap = personCollection.stream()
				.collect(Collectors.groupingBy(Person::getName));
	}

通过 Stream API,Collection 集合可以调用方法直接转换成其他的集合或者 Map 类。

如上面的案例所示,常用的一般有:

  1. 转换 List:collect(Collectors.toList());
  2. 转换 Set:collect(Collectors.toSet());
  3. 转换 Map:collect(Collectors.toMap(Person::getName, v -> v, (k1, k2) -> k1)),Person::getName 指定 Map 的 KEY,v -> v 指定 Map 的 VALUE,而最后的 (k1, k2) -> k1,指定 KEY 相同时,以哪个为准,强烈要求传入 (k1, k2) -> k1 方法,否则 KEY 冲突时会报错;另外也可以用 collect(Collectors.groupingBy(Person::getName)) 对集合进行分组。

求最大/小值、求和、求均值

	public static void calculate(Collection<Person> personCollection) {
		// 求最大年龄
		Optional<Integer> maxAge = personCollection.stream()
				.map(Person::getAge).max(Integer::compareTo);
		// 效果同上
		// OptionalInt max = personCollection.stream().mapToInt(Person::getAge).max();

		// 求最小年龄
		Optional<Integer> minAge = personCollection.stream()
				.map(Person::getAge).min(Integer::compareTo);
		// 效果同上
		// OptionalInt min = personCollection.stream().mapToInt(Person::getAge).min();

		// 年龄求和
		int sum = personCollection.stream()
				.mapToInt(Person::getAge).sum();
		// 年龄求平均值
		OptionalDouble average = personCollection.stream()
				.mapToInt(Person::getAge).average();
	}

上面的例子展示了通过 Stream API 来对集合求最大/小值、求和、求均值等操作,主要区别我都特地换行了,方便区分。主要就是要通过 map 方法先映射对象中你想要计算的字段值,然后再调用相应的求值方法,比如 max。另外如果调用的是 map 方法,则返回的依旧是 Stream,而调用了 mapToXXX(比如 mapToInt) 则返回的是相应的 XXXStream(比如 IntStream),IntStream 主要是返回的 Optional 类不同和求值方法(max、min 等)不用传参。

七、新的时间类

Java 8 中,新增了不少功能齐全的时间类,常用的比如 LocalDateTime、LocalDate、LocalTime 等,这些新的时间类都放在 java.time 包下,感兴趣的可以自己到这个包下研究底层源码。

优点

  1. 旧的 Date、Calendar 类线程非安全,而新的 LocalDateTime、LocalDate、LocalTime 等线程安全,官方注释中明确的写明了:Implementation Requirements: This class is immutable and thread-safe;
  2. 旧的 Date 类月份从 0 开始,也就是 1~12 月份对应的值其实是 0 - 11,一般要使用 Calendar 中的月份枚举,不然很容易搞错;
  3. 新的 LocalDateTime(年月日 时分秒)、LocalDate(年月日) 和 LocalTime (时分秒)将时间拆分得更加具体,并且三个类之间可以互相转换,可以根据业务需求使用对应的类;
  4. 新的 LocalDateTime 等时间类,API 更加齐全,封装了对应的年与日,时分秒的获取方法,还有加(plus)减(minus)方法。

LocalDateTime、LocalDate、LocalTime

	public static void convert() {
		// 2022-03-28T00:00
		LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2022, 3, 28), LocalTime.MIN);

		// 2022-03-28
		LocalDate localDate = dateTime.toLocalDate();
		// 00:00
		LocalTime localTime = dateTime.toLocalTime();

		System.out.println(dateTime);
		System.out.println(localDate);
		System.out.println(localTime);
	}

日期计算

	public static void datetimeComputer() {
		LocalDateTime current = LocalDateTime.now();

		// 加法
		LocalDateTime nextYear = current.plusYears(1);
		LocalDateTime nextMonth = current.plusMonths(1);
		LocalDateTime tomorrow = current.plusDays(1);
		LocalDateTime nextHour = current.plusHours(1);
		LocalDateTime nextMinute = current.plusMinutes(1);
		LocalDateTime nextSecond = current.plusSeconds(1);

		// 对应的减法就是 minusXXX,获取就是 getXXX,此处不在重复累赘
		LocalDateTime lastYear = current.minusYears(1);
		// ...
		int currentYear = current.getYear();
		// ...
	}

日期解析和格式化

日期的解析和格式化都需要依赖 DateTimeFormatter 类,DateTimeFormatter 类主要定义了日期的格式,如下:

	public static void parseAndFormat() {
		LocalDateTime current = LocalDateTime.now();

		// 定义日期模板
		final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

		// 格式化
		String dateTimeString = current.format(formatter);
		// 解析
		LocalDateTime parseTime = LocalDateTime.parse(dateTimeString, formatter);
		// 获取秒级时间戳
		long timestampSec = parseTime.atZone(ZoneId.systemDefault()).toEpochSecond();
		// 获取毫秒级时间戳
		long timestampMilliSec = parseTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
	}

还有其他新的时间类,此处就不再展开介绍,后续自行了解。

参考资料:https://www.oracle.com/java/technologies/javase/8-whats-new.html,Oracle Java 8 官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值