Java8新特性详解
一、Lambda 表达式
Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。
基本语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
基本的Lambda例子
假设有一个玩家List ,程序员可以使用 for 语句 ("for 循环")来遍历,在Java SE 8中可以转换为另一种形式:
public class LambdaDemo { public static void main(String[] args) { String[] atp = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer", "Roger Federer", "Andy Murray", "Tomas Berdych", "Juan Martin Del Potro"}; List<String> players = Arrays.asList(atp); // 以前的循环方式 for (String player : players) { System.out.print(player + "; "); } // 使用 lambda 表达式以及函数操作(functional operation) players.forEach((player) -> System.out.print(player + "; ")); // 在 Java 8 中使用双冒号操作符(double colon operator) players.forEach(System.out::println); } }
lambda表达式可以将我们的代码缩减到一行。 另一个例子是在图形用户界面程序中,匿名类可以使用lambda表达式来代替。 同样,在实现Runnable接口时也可以这样使用:
// 使用匿名内部类 btn.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("Hello World!"); } }); // 或者使用 lambda expression btn.setOnAction(event -> System.out.println("Hello World!"));
lambda实现 Runnable接口 的示例:
// 1.1使用匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }).start(); // 1.2使用 lambda expression new Thread(() -> System.out.println("Hello world !")).start(); // 2.1使用匿名内部类 Runnable race1 = new Runnable() { @Override public void run() { System.out.println("Hello world !"); } }; // 2.2使用 lambda expression Runnable race2 = () -> System.out.println("Hello world !"); // 直接调用 run 方法(没开新线程哦!) race1.run(); race2.run();
Runnable 的 lambda表达式,使用块格式,将五行代码转换成单行语句。
使用Lambda排序集合
在Java中,Comparator 类被用来排序集合。 在下面的例子中,我们将根据球员的 name, surname, name 长度 以及最后一个字母,先使用匿名内部类来排序,然后再使用lambda表达式精简我们的代码。
根据name来排序list。 使用旧的方式,代码如下所示:
String[] players = {"Rafael Nadal", "Novak Djokovic", "Stanislas Wawrinka", "David Ferrer", "Roger Federer", "Andy Murray", "Tomas Berdych", "Juan Martin Del Potro", "Richard Gasquet", "John Isner"}; // 1.1 使用匿名内部类根据 name 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.compareTo(s2)); } });
使用Lambda可以通过下面的代码实现同样的功能:
// 1.2 使用 lambda expression 排序 players Comparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2)); Arrays.sort(players, sortByName); // 1.3 也可以采用如下形式: Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
其他的排序例子如下:
// 1.1 使用匿名内部类根据 surname 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" ")))); } }); // 1.2 使用 lambda expression 排序,根据 surname Comparator<String> sortBySurname = (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ); Arrays.sort(players, sortBySurname); // 1.3 或者这样,怀疑原作者是不是想错了,括号好多... Arrays.sort(players, (String s1, String s2) -> ( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) ) ); // 2.1 使用匿名内部类根据 name lenght 排序 players Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.length() - s2.length()); } }); // 2.2 使用 lambda expression 排序,根据 name lenght Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length()); Arrays.sort(players, sortByNameLenght); // 2.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length())); // 3.1 使用匿名内部类排序 players, 根据最后一个字母 Arrays.sort(players, new Comparator<String>() { @Override public int compare(String s1, String s2) { return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); } }); // 3.2 使用 lambda expression 排序,根据最后一个字母 Comparator<String> sortByLastLetter = (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)); Arrays.sort(players, sortByLastLetter); // 3.3 or this Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
使用Lambda和Stream
Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。
我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的POJO类:
public class Person { private String firstName; private String lastName; private String job; private String gender; private Integer salary; private Integer age; public Person(String firstName, String lastName, String job, String gender, Integer age, Integer salary) { this.firstName = firstName; this.lastName = lastName; this.job = job; this.gender = gender; this.salary = salary; this.age = age; } // Getter and Setter // . . . . . }
我们将创建两个list,都用来存放Person对象:
List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500)); add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600)); add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900)); add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700)); add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000)); add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300)); } }; List<Person> phpProgrammers = new ArrayList<Person>() { { add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550)); add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200)); add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600)); add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000)); add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100)); add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300)); add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100)); add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000)); add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600)); add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800)); } };
现在我们使用forEach方法来迭代输出上述列表:
System.out.println("所有程序员的姓名:"); javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
另一个有用的方法是过滤器filter() ,让我们显示月薪超过1400美元的PHP程序员:
System.out.println("下面是月薪超过 $1,400 的PHP程序员:"); phpProgrammers.stream() .filter((p) -> (p.getSalary() > 1400)) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
我们也可以定义过滤器,然后重用它们来执行其他操作:
// 定义 filters Predicate<Person> ageFilter = (p) -> (p.getAge() > 25); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender())); System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:"); phpProgrammers.stream() .filter(ageFilter) .filter(salaryFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); // 重用filters System.out.println("年龄大于 24岁的女性 Java programmers:"); javaProgrammers.stream() .filter(ageFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
使用limit方法,可以限制结果集的个数:
System.out.println("最前面的3个 Java programmers:"); javaProgrammers.stream() .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName())); System.out.println("最前面的3个女性 Java programmers:"); javaProgrammers.stream() .filter(genderFilter) .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
排序呢? 我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:
System.out.println("根据 name 排序,并显示前5个 Java programmers:"); List<Person> sortedJavaProgrammers = javaProgrammers .stream() .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName()))) .limit(5) .collect(Collectors.toList()); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName())); System.out.println("根据 salary 排序 Java programmers:"); sortedJavaProgrammers = javaProgrammers .stream() .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) ) .collect(Collectors.toList()); sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法:
System.out.println("工资最低的 Java programmer:"); Person pers = javaProgrammers .stream() .min((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary()); System.out.println("工资最高的 Java programmer:"); Person person = javaProgrammers .stream() .max((p, p2) -> (p.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary());
上面的例子中我们已经看到 collect 方法是如何工作的。 结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:
System.out.println("将 PHP programmers 的 first name 拼接成字符串:"); String phpDevelopers = phpProgrammers .stream() .map(Person::getFirstName) .collect(Collectors.joining(" ; ")); // 在进一步的操作中可以作为标记(token) System.out.println("将 Java programmers 的 first name 存放到 Set:"); Set<String> javaDevFirstName = javaProgrammers .stream() .map(Person::getFirstName) .collect(Collectors.toSet()); System.out.println("将 Java programmers 的 first name 存放到 TreeSet:"); TreeSet<String> javaDevLastName = javaProgrammers .stream() .map(Person::getLastName) .collect(Collectors.toCollection(TreeSet::new));
Streams 还可以是并行的(parallel)。 示例如下:
System.out.println("计算付给 Java programmers 的所有money:"); int totalSalary = javaProgrammers .parallelStream() .mapToInt(p -> p.getSalary()) .sum(); System.out.println(totalSalary);
我们可以使用summaryStatistics方法获得stream 中元素的各种汇总数据。 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage:
//计算 count, min, max, sum, and average for numbers List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers .stream() .mapToInt((x) -> x) .summaryStatistics(); System.out.println("List中最大的数字 : " + stats.getMax()); System.out.println("List中最小的数字 : " + stats.getMin()); System.out.println("所有数字的总和 : " + stats.getSum()); System.out.println("所有数字的平均值 : " + stats.getAverage());
二、函数式接口
从 Java 8 开始便出现了函数式接口(Functional Interface,以下简称FI)。
如果一个接口只有唯一的一个抽象接口,则称之为函数式接口。为了保证接口符合 FI ,通常会在接口类上添加 @FunctionalInterface 注解。
函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以对现有的函数友好地支持 lambda。
/** * 文件描述 函数式接口:有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 **/ @FunctionalInterface public interface Hello { /** * abstract 方法,只能有一个 */ void hello(); /** * 允许定义默认方法 */ default void hi(){ System.out.println("this is default method"); } /** * 允许定义静态方法 */ static void hei() { System.out.println("this is static method"); } /** * 允许定义 java.lang.Object 里的 public 方法 */ @Override boolean equals(Object obj);
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function包
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口 43 个,但是最主要的是这四个:
(1)功能性接口:Function<T,R>
(2)断言性接口:Predicate<T>
(3)供给性接口:Supplier<T>
(4)消费性接口:Consumer<T>
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer | T | void | 对类型T参数操作,无返回结果,包含方法 void accept(T t) |
Supplier | 无 | T | 返回T类型参数,方法时 T get() |
Function | T | R | 对类型T参数操作,返回R类型参数,包含方法 R apply(T t) |
Predicate | T | boolean | 断言型接口,对类型T进行条件筛选操作,返回boolean,包含方法 boolean test(T t) |
/** * Java8内置的四大核心函数式接口: * Consumer<T>:消费型接口</T> * Supplier<T>供给型接口</T> * Function<T,R>函数型接口</T,R> * Predicate<T>断言型接口</T> */ public class TestLamda3 { //Consumer<T> public void test1() { happy(10000, (m) -> System.out.println("这次消费了" + m + "元")); } public void happy(double money, Consumer<Double> con) { con.accept(money); } //Supplier<T> public void test2() { List<Integer> list = getNumList(5, () -> { return (int) Math.random() * 100; }); list.forEach(System.out::println); } public List<Integer> getNumList(int num, Supplier<Integer> supplier) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < num; i++) { Integer n = supplier.get(); list.add(n); } return list; } //函数式接口 public void test4() { String newStr = strHandle("\t\t\t woshi nide ", (str) -> str.trim()); System.out.println(newStr); } public String strHandle(String str, Function<String, String> fun) { return fun.apply(str); } //断言型接口;将满足条件的字符串放入集合中 public void test5() { List<String> list1 = Arrays.asList("nihao", "hiehei", "woai", "ni"); List<String> list = filterStr(list1, (s) -> s.length() > 3); for (String s : list) { System.out.println(s); } } public List<String> filterStr(List<String> list, Predicate<String> pre) { List<String> strings = new ArrayList<>(); for (String string : list) { if (pre.test(string)) { strings.add(string); } } return strings; }
全部接口:
序号 | 接口 & 描述 |
---|---|
1 | BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate<T,U>
代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier
代表了boolean值结果的提供方 |
6 | Consumer<T>
代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate
代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier
代表一个double值结构的提供方 |
12 | DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R>
接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer
接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction<R>
接受一个int类型输入参数,返回一个结果 。 |
19 | IntPredicate
接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier
无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction
接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer
接受一个long类型的输入参数,无返回值。 |
26 | LongFunction<R>
接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier
无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction
接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate<T>
接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier<T>
无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。 |
三、Optional的使用
java8 推出的Optional的目的就是为了杜绝空指针异常,帮助开发者开发出更优雅的代码,使用Optional不正确时,将会违背设计者的初衷。
optional的构造方式
(1) Optional.of(T)
该方式的入参不能为null,否则会有NPE,在确定入参不为空时使用该方式。
(2) Optional.ofNullable(T)
该方式的入参可以为null,当入参不确定为非null时使用。
(3) Optional.empty()
这种方式是返回一个空Optional,等效Optional.ofNullable(null)
如何正确使用Optional
尽量避免使用的地方:
- 避免使用Optional.isPresent()来检查实例是否存在,因为这种方式和null != obj没有区别,这样用就没什么意义了。
- 避免使用Optional.get()方式来获取实例对象,因为使用前需要使用Optional.isPresent()来检查实例是否存在,否则会出现NPE问题。
- 避免使用Optional作为类或者实例的属性,而应该在返回值中用来包装返回实例对象。
- 避免使用Optional作为方法的参数,原因同3。
正确使用方式:
(1) 实例对象存在则返回,否则提供默认值或者通过方法来设置返回值,即使用orElse/orElseGet方式:
//存在则返回 User king = new User(1, "king"); Optional<User> userOpt = Optional.of(king); User user = userOpt.orElse(null); System.out.println(user.getName());
//不存在提供默认值 User user2 = null; Optional<User> userOpt2 = Optional.ofNullable(user2); User user3 = userOpt2.orElse(unknown); System.out.println(user3.getName());
//通过方法提供值 User user4 = userOpt2.orElseGet(() -> new User(0, "DEFAULT")); System.out.println(user4.getName())
不建议这样使用:
if(userOpt.isPresent()) { System.out.println(userOpt.get().getName()); } else { //。。。 }
(2) 使用ifPresent()来进行对象操作,存在则操作,否则不操作。
//实例存在则操作,否则不操作 userOpt.ifPresent(u -> System.out.println(u.getName())); userOpt2.ifPresent(u -> System.out.println(u.getName()));
(3) 使用map/flatMap来获取关联数据
//使用map方法获取关联数据 System.out.println(userOpt.map(u -> u.getName()).orElse("Unknown")); System.out.println(userOpt2.map(u -> u.getName()).orElse("Default")); //使用flatMap方法获取关联数据 List<String> interests = new ArrayList<String>(); interests.add("a");interests.add("b");interests.add("c"); user.setInterests(interests); List<String> interests2 = Optional.of(user) .flatMap(u -> Optional.ofNullable(u.getInterests())) .orElse(Collections.emptyList()); System.out.println(interests2.isEmpty());
四、日期时间
Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样。
以下为一些常用时间对象:
- Instant:表示时刻,不直接对应年月日信息,需要通过时区转换
- LocalDateTime: 表示与时区无关的日期和时间信息,不直接对应时刻,需要通过时区转换
- LocalDate:表示与时区无关的日期,与LocalDateTime相比,只有日期信息,没有时间信息
- LocalTime:表示与时区无关的时间,与LocalDateTime相比,只有时间信息,没有日期信息
- ZonedDateTime: 表示特定时区的日期和时间
- ZoneId/ZoneOffset:表示时区
Clock 时钟
Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
TimeZones 时区
在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]
LocalTime 时间
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37
常用方法:
LocalTime now = LocalTime.now(); //指定时区 LocalTime now1 = LocalTime.now(Clock.system(ZoneId.systemDefault())); LocalTime now2 = LocalTime.now(Clock.systemUTC()); System.out.println(now); System.out.println(now1); System.out.println(now2); System.out.println("************now************"); //清除毫秒位 System.out.println(now.withNano(0)); //获取当前的小时 System.out.println(now.getHour()); //解析时间时间也是按照ISO格式识别,但可以识别以下3种格式: 12:00 12:01:02 12:01:02.345 System.out.println(LocalTime.parse("11:58:12")); //时间比较 LocalTime other = LocalTime.of(13, 45, 59); System.out.println(now.isBefore(other)); System.out.println(now.isAfter(other));
LocalDate 本地日期
LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY
从字符串解析一个LocalDate类型和解析LocalTime一样简单:
DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24
常用方法:
LocalDate now = LocalDate.now(); LocalDate now1 = LocalDate.now(Clock.systemUTC()); System.out.println(now); System.out.println(now1); LocalDate of = LocalDate.of(2019, 3, 6); //严格按照ISO yyyy-MM-dd验证,03写成3都不行 LocalDate parse = LocalDate.parse("2019-03-06"); System.out.println(of); System.out.println(parse); System.out.println("**************now****************"); //当前开始时间 System.out.println(now.atStartOfDay()); //当月第一天日期 System.out.println(now.with(TemporalAdjusters.firstDayOfMonth())); //本月第二天日期 System.out.println(now.withDayOfMonth(2)); //当月最后一天 System.out.println(now.with(TemporalAdjusters.lastDayOfMonth())); System.out.println(now.getDayOfMonth()); //当月下一天 System.out.println(now.plusDays(1)); //当月上一天 System.out.println(now.minusDays(1)); System.out.println(now.getDayOfWeek()); //当月下一周 System.out.println(now.plusWeeks(1)); //当月上一周 System.out.println(now.minusWeeks(1)); System.out.println(now.getMonth() + "-" + now.getMonthValue()); //当月下一个月 System.out.println(now.plusMonths(1)); //当月上一个月 System.out.println(now.minusMonths(1)); //时间比较 System.out.println(now.isEqual(LocalDate.of(2019, 03, 06)));
LocalDateTime 本地日期时间
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
Date转换为LocalDateTime:
/** * Date转换为LocalDateTime * @return LocalDateTime */ public static LocalDateTime date2LocalDateTime() { Date date = new Date(); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); return localDateTime; }
LocalDateTime转换为Date:
/** * LocalDateTime转换为Date * @return Date */ public static Date localDateTime2Date() { LocalDateTime now = LocalDateTime.now(); Instant instant = now.atZone(ZoneId.systemDefault()).toInstant(); Date date = Date.from(instant); return date; }
时间的比较:
LocalDateTime from = LocalDateTime.of(2018, Month.OCTOBER, 1, 0, 0, 0); LocalDateTime to = LocalDateTime.of(2019, Month.MARCH, 6, 23, 59, 59); Duration between = Duration.between(from, to); System.out.println(between.toDays()); System.out.println(between.toHours());
常用方法:
LocalDateTime now = LocalDateTime.now(); LocalDateTime now1 = LocalDateTime.now(Clock.system(ZoneId.systemDefault())); LocalDateTime now2 = LocalDateTime.now(Clock.systemUTC()); System.out.println(now); System.out.println(now1); System.out.println(now2); //时间格式转换 System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_1))); System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_2))); System.out.println(now.format(DateTimeFormatter.ofPattern(PATTERN_3)));
常用API总结
public static void main(String[] args) { /** * java.time包中的类是不可变且线程安全的,有以下关键类: * Instant —— 它代表的是时间戳 * LocalDate —— 不包含具体时间的日期, 比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。 * LocalTime —— 不含日期的时间 * LocalDateTime —— 它包含了日期及时间,不过还是没有偏移信息或者说时区。 * ZonedDateTime —— 这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。 */ /** 获取当天的日期 **/ LocalDate today = LocalDate.now(); //今天日期是2020-02-18 System.out.println("今天日期是:" + today); /** 获取当前的年月日 **/ LocalDate localDate = LocalDate.now(); int year = localDate.getYear(); int month = localDate.getMonthValue(); int day = localDate.getDayOfMonth(); System.out.println("年:" +year+"\n月:" +month+"\n日:" + day); /** 检查两个日期是否相等 : LocalDate重写了equals方法来进行日期的比较 **/ LocalDate date = LocalDate.of(2016, 4, 21); LocalDate curDay = LocalDate.now(); System.out.println("日期相等吗?" + date.equals(curDay)); /** 获取当前时间(不包含日期) : 默认的格式是hh:mm:ss:nnn **/ LocalTime localTime = LocalTime.now(); System.out.println("现在时间是:" + localTime); /** 如何增加时间里面的小时数:plusXxx **/ LocalTime afterTwo = localTime.plusHours(2); System.out.println("两个小时后,时间是:" + afterTwo); /** 如何减少时间里面的小时数:minusXxx **/ /** * 如何获取1周后的日期 * LocalDate是用来表示无时间的日期,它有一个plus()方法可以用来增加日,星期,月, * ChronoUnit则用来表示时间单位,LocalDate也是不可变的,因此任何修改操作都会返回一个新的实例 * **/ LocalDate curDate = LocalDate.now(); //我们可以用这个方法来增加一个月,一年,一小时,一分等等 LocalDate afterOneWeeks = curDate.plus(1, ChronoUnit.WEEKS); //LocalDate afterOneWeeks = curDate.plusWeeks(1); System.out.println("1周后日期是:" + afterOneWeeks); /** * 时钟 * java8自带了Clock类,可以用来获取某个时区下(所以对时区是敏感的)当前的瞬时时间、日期。 * 用来代替System.currentTimelnMillis()与TimeZone.getDefault()方法 */ //根据系统时钟或UTC返回当前时间 Clock clock = Clock.systemUTC(); //Clock clock = Clock.systemDefaultZone(); System.out.println("clock:" + clock); /** * 如何判断某个日期在另一个日期的前面还是后面或者相等, * 在java8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期。 * 如果调用方法的那个日期比给定的日期要早的话,isBefore()方法会返回true。 */ /** * 如何在java8中检查闰年 * LocalDate类由一个isLeapYear()方法来返回当前LocalDate对应的那年是否是闰年 */ /** * 两个日期之间包含多少天,多少月 * 计算两个日期之间包含多少天、周、月、年。可以用java.time.Period类完成该功能。 */ LocalDate curDe = LocalDate.now(); LocalDate fixDate = LocalDate.of(2016, 4, 21); Period period = Period.between(curDe, fixDate); System.out.printf("日期%s和日期%s相差%s个月", curDe, fixDate, period.getMonths()); /** * 在java8中获取当前时间戳 * Instant类由一个静态的工厂方法now()可以返回当前时间戳 * 事实上Instant就是java8以前的Date,可以使用这个两个类中的方法在这两个类型之间进行转换, * 比如Date.from(Instant)就是用来把Instant转换成java.util.date的, * 而Date。toInstant()就是将Date转换成Instant的 */ Instant timestamp = Instant.now(); /** * 如何在java8中使用预定义的格式器来对日期进行解析/格式化 * 在java8之前,时间日期的格式化非常麻烦,经常使用SimpleDateFormat来进行格式化, * 但是SimpleDateFormat并不是线程安全的。在java8中,引入了一个全新的线程安全的日期与 * 时间格式器。并且预定义好了格式。 */ String dateStr = "20180404"; LocalDate localDate1 = LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE); System.out.println("格式化的日期为:" + localDate1); /** * 如何在java中使用自定义的格式器来解析日期 * 有时预置的不能满足的时候就需要我们自定义日期格式器了,下面的例子中的日期格式是"MM dd yyyy". * 你可以给DateTimeFormatter的ofPattern静态方法()传入任何的模式,它会返回一个实例 */ DateTimeFormatter mmDdYyyy = DateTimeFormatter.ofPattern("MM dd yyyy"); /** * 如何在java8中对日期进行格式化,转换成字符串 * LocalDate.format()这个方法会返回一个代表当前日期的字符串 */ LocalDateTime localDateTime = LocalDateTime.now(); DateTimeFormatter yyyyMMddHHmmss = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); String ds = localDateTime.format(yyyyMMddHHmmss); System.out.println("日期格式后:" + ds); }