0、java新特性简介
-
速度更快
数据结构发生变化
jvm内存模型变化 新生代、老年代、永久区、方法计数器、栈
新的垃圾回收机制
代码更少
强大的Steam API
便于并行
最大化减少空指针异常Optional
1. Lambda表达式
Lambda 是一个匿名函数,我们可以吧Lambda表达式理解成一段可以传递的代码
(将代码像数据一样传递)。可以携程更简洁、更灵活的代码。作为一种更紧凑的代码峰哥,使Java的语言表达能力得到了提升。
1.1. 基本语法
1.1.1 匿名内部类
@Test
public void anonymousClassTest() {
// 匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
1.1.2. Lambda表达式
@Test
public void lambdaTest() {
// Lambda表达式
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts = new TreeSet<>(comparator);
}
1.1.3. Lambda表达式基础语法
箭头操作符(Lambda操作符)将Lambda表达式拆分成两部分:
- 左侧:lambda表达式的参数列表
- 右侧:lambda表达式中所需要执行的功能,即 Lambda体
语法格式
-
无参数,无返回值 比如 Runable接口
() -> SystemOut.out.println("Hello World!");
-
有一个参数,无返回值
Consumer<String> consumer = (x) -> System.out.println("Hello " + x); consumer.accept("World");
只有一个参数的情况下,可以将参数的小括号去掉。
Consumer<String> consumer = x -> System.out.println("Hello " + x); consumer.accept("World");
-
有多个参数,且有返回值。并且Lambda体中有多条语句,多条语句,必须加大括号
Comparator<Integer> com = (x, y) -> { System.out.println("调用比较函数"); return Integer.compare(o1, o2); };
-
有多个参数,且有返回值,并且Lambda体中只有一条语句,那么lambda体中的大括号可以省略,并且return也可以省略
Comparator<Integer> com = (x, y) -> Integer.compare(o1, o2);
-
Lambda表达式的参数列表的数据类型可以省略不写,因为JVM表一起通过上下文可以推断出数据类型,既 类型推断
List<String> stringList = new ArrayList<>();
1.2. 函数式接口
函数式接口:接口中只有一个抽象方法的接口,成为函数式接口。可以使用注解@FunctionalInterface
修饰,注解的作用是用来检查接口是否是一个函数式接口。
1.2.1. java8内置的四大函数式接口
java8在rt.jar中的 java.util.function
包里提供了很多函数式接口
-
Consumer<T>
: 消费型接口
void accept(T t);
-
Supplier<T>
:供给型接口
T get();
-
Function<T,R>
:函数型接口
R apply(T t);
-
Predicate<T>
:断言型接口
boolean test(T t);
1.3. 方法引用与构造器引用
1.3.1 方法引用
方法引用:若Lambda提中的内容已经有方法实现了,我们可以使用方法引用。
可以理解为方法引用是Lambda表达式的另一种表现形式。
主要有三种语法格式:
- 对象::实例方法
- 类 ::静态方法名
- 类::实例方法名
-
对象::实例方法
如果方法体中方法已经由其他对象的方法实现了,那么就可以使用
obj::method
来替代方法体。Consumer<String> con = x -> System.out.println(x);
方法体中只有一个已经由System.out对象实现的方法,并且方法的参数和返回值和接口方法的参数和返回值都一致,所以可以使用方法引用。
Consumer<String> con = System.out::print;
-
类::静态方法
与上面类似,如果一个静态方法可以实现某个函数式接口的抽象类方法,那么可以使用这种方式来实现。
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
Comparator<Integer> com = Integer::compare;
-
类::实例方法名
这种方式有点特殊,因为实例方法并不是类来调起的,而是由第一个参数调起的。
BiFunction<String, String, Boolean> biFunction = (x, y) -> x.equals(y);
BiFunction<String, String, Boolean> biFunction = String::equals;
这种情况下,第一个参数是方法的调用者,第二个参数是方法的参数。
1.3.2. 构造器引用
格式:ClassName::new
Supplier<Employee> sup = () -> new Employee();
Supplier<Employee> sup = Employee::new;
这种情况下,调用的构造器,和函数式接口中的抽象方法的参数列表一致。如果没有参数则调用无参构造器,如果有参,则调用的构造器参数列表和函数参数列表一致。
2. Optional类
Optional<T>
类(java.util.Optional
)是一个容器类,代表一个值存在或者不存在,原来用哪个null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常。
常用方法:
方法 | 功能 |
---|---|
of(T t) | 创建一个Optional实例 |
empty() | 创建一个空的Optional实例 |
ofNullable(T t) | 若t不为null,创建Optional实例,否则创建空实例 |
isPresent() | 判断你是否包含值 |
orElse(T t) | 如果d调用对象呢包含哪只,返回该值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回s获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty() |
3. Steam API
Stream是Java8中处理几何的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、和映射数据等操作,使用Stream API对几何数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API(java.util.stream)来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
2.1. 概念
Stream是数据渠道,用户操作数据源(集合、数组等)所生成的元素序列。集合讲得是数据,流讲得是计算。
注意:
- Stream自己不会存储元素。
- Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream。
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。
2.2. Stream操作
- 创建Stream:从一个数据源(如:集合、数组)中获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理。
- 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果。
2.2.1. 创建Stream
//1.可以通过Collection 系列集合提供的stream()或 parallelStream()方法获取串行流或并行流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream()方法获取流
String[] strs = new String[10];
Stream<String> stream2 = Arrays.stream(strs);
//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc");
//4.创建无限流
// 迭代 (0,2, 4, 6, 8, ...)
Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);
//生成 (无限随机数)
Stream.generate(() -> Math.random());
2.2.2. 中间操作
筛选与切片
名称 | 作用 |
---|---|
filter | 接收lambda,从流中排除某些元素 |
limit | 截断流,使其元素不超过给定数量 |
skip(n) | 跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不足n个,则返回一个空流,与limit互补 |
map | 映射,接受Lambda,将元素转换成其他形式。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
distinct | 筛选,通过流所生成的元素的hashCode()和equals()去除重复元素 |
-
准备
private List<Employee> employees; @Before public void before() { employees = new ArrayList<>(); Employee zhangsan = new Employee("张三", 25, 5000.1); Employee lisi = new Employee("李四", 28, 8000.2); Employee wangwu = new Employee("王五", 45, 20000.30); Employee zhaoliu = new Employee("赵六", 37, 15000.5); Employee sunqi = new Employee("孙七", 35, 18000.5); employees.add(zhangsan); employees.add(lisi); employees.add(wangwu); employees.add(zhaoliu); employees.add(sunqi); }
-
filter
@Test public void testFilter() throws InterruptedException { // 中间操作,不会执行任何操作 Stream s = employees.stream() .filter(e -> { System.out.println("Stream API 的中间操作"); return e.getAge() > 35; }); System.out.println("============="); Thread.sleep(1000L); //终止操作:一次性执行全部内容,即惰性求值 s.forEach(System.out::println); }
数据结果:
============= Stream API 的中间操作 Stream API 的中间操作 Stream API 的中间操作 Employee{name='王五', age=45, salary=20000.3} Stream API 的中间操作 Employee{name='赵六', age=37, salary=15000.5} Stream API 的中间操作 Process finished with exit code 0
-
limit
@Test public void testLimit() { employees.stream() .filter(e -> { System.out.println("filter 操作"); return e.getSalary() > 8000; }) .limit(2) .forEach(System.out::println); }
结果:
filter 操作 filter 操作 Employee{name='李四', age=28, salary=8000.2} filter 操作 Employee{name='王五', age=45, salary=20000.3} Process finished with exit code 0
-
skip
@Test public void testSkip() { employees.stream() .filter(e -> e.getSalary() > 8000) .skip(2) .forEach(System.out::println); }
结果:
Employee{name='赵六', age=37, salary=15000.5} Employee{name='孙七', age=35, salary=18000.5} Process finished with exit code 0
-
map
@Test public void testMap() { Stream.of("aaa", "bbb", "ccc", "ddd") .map(str -> str.toUpperCase()) .forEach(System.out::println); }
结果:
AAA BBB CCC DDD Process finished with exit code 0
-
sorted
//排序 @Test public void testSored() { employees.stream() .sorted((e1, e2) -> { if(e1.getAge() == e2.getAge()){ return e1.getName().compareTo(e2.getName()); } else { return e1.getAge() - e2.getAge(); } }) .forEach(System.out::println); }
结果:
Employee{name='张三', age=25, salary=5000.1} Employee{name='李四', age=28, salary=8000.2} Employee{name='孙七', age=37, salary=18000.5} Employee{name='赵六', age=37, salary=15000.5} Employee{name='王五', age=45, salary=20000.3} Process finished with exit code 0
2.2.3. 终止操作
2.2.3.1. 查找与匹配
名称 | 作用 |
---|---|
allMatch | 检查是否配所有元素 |
anyMatch | 检查是否至少匹配一个元素 |
noneMatch | 检查是否没有匹配任何元素 |
findFirst | 返回第一个元素 |
findAny | 返回当前流中的任意元素 |
count | 返回流中元素的总个数 |
max | 返回流中的最大值 |
min | 返回流中的最小值 |
forEach(Consumer) | 内部迭代 |
-
allMatch
@Test public void testAllMatch() { boolean res = employees.stream() .filter(e -> e.getAge() > 35) .allMatch(e -> e.getSalary() > 10000); System.out.println(res); }
-
anyMatch
@Test public void testAnyMatch() { boolean res = employees.stream() .filter(e -> e.getAge() > 35) .anyMatch(e -> e.getSalary() > 20000); System.out.println(res); }
2.2.3.2. 规约
reduce(T identity, BinaryOperator)
、reduce(BinaryOperator)
---- 可以将流中元素反复结合起来,得到一个值。
名称 | 作用 |
---|---|
reduce(T identity, BinaryOperator) | 可以将流中元素反复结合起来,得到一个值,返回T |
reduce(BinaryOperator) | 可以将流中元素反复结合起来,得到一个值,返回Optional<T> |
@Test
public void testReduce() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println("====================");
Optional<Double> op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
}
2.2.3.3. 收集
collect
----将流转换成其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。
名称 | 作用 |
---|---|
collect(Collector) | 将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector接口中的方法的实现呢,决定了如何对流执行收集操作,如收集到List、Set、Map中。但是Collectors实用类提供了很多静态方法,可以方便地创建常用的收集器实例,具体的方法和实例如下表:
-
将流元素归约汇总成一个值
方法 作用 counting 统计数量 summingInt 求和 averagingInt 求平均值 maxBy 最大值 minBy 最小值 summary 这个收集器是上面所有功能的综合,使用对应的方法可以获取所有的值 joining 将字符串连接在一起 reducing 和前面reduce功能类似 @Test public void testCollect() { // 员工数量 Long count = employees.stream() .collect(Collectors.counting()); System.out.println("总数: " + count); // 求所有员工的总工资 Double sumarySalary = employees.stream() .collect(Collectors.summingDouble(Employee::getSalary)); System.out.println("工资总和: " + sumarySalary); //求所有员工的平均年龄 Double averageSalary = employees.stream() .collect(Collectors.averagingDouble(Employee::getSalary)); System.out.println("员工平均工资:" + averageSalary); //求最高工资 // Optional<Employee> maxSalar = employees.stream() // .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); Optional<Employee> maxSalar = employees.stream() .collect(Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))); System.out.println("员工最高工资:" + maxSalar.get()); //求最低工资 Optional<Employee> minSalar = employees.stream() .collect(Collectors.minBy(Comparator.comparingDouble(Employee::getSalary))); System.out.println("员工最低工资:" + minSalar.get()); //求上面所有内容 DoubleSummaryStatistics collect = employees.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println("员工最高工资:" + collect.getMax() ); System.out.println("员工总工资:" + collect.getSum() ); //... // 将员工名连接成一个字符串,使用逗号分隔 前后用括号包围 String empNames = employees.stream() .map(Employee::getName) .collect(Collectors.joining(",", "(", ")")); System.out.println("所有员工名:" + empNames); // Double sumSal = employees.stream() // .collect(Collectors.reducing(0.0, Employee::getSalary, (x, y) -> x + y)); Double sumSal = employees.stream() .collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum)); System.out.println("reducing 总工资:" + sumSal); }
运行结果:
总数: 5 工资总和: 66001.6 员工平均工资:13200.320000000002 员工最高工资:Employee{name='王五', age=45, salary=20000.3} 员工最低工资:Employee{name='张三', age=25, salary=5000.1} 员工最高工资:20000.3 员工总工资:66001.6 所有员工名:(张三,李四,王五,赵六,孙七) reducing 总工资:66001.6
-
元素分组
分组使用groupingBy类似于Sql中的group by 根据某个字段进行分组。可以配合多个收集器使用。
方法 作用 多级分组 根据多个字段进行分组 groupingBy+maxBy 获取每个分组中最大值 groupingBy+counting 获取每个分组的元素数量 groupingBy+mapping 对每个分组进行处理 @Test public void testGroupBy() { // 多级分组 Map<String, Map<String, List<Employee>>> groupMap = employees.stream() .collect(Collectors.groupingBy(Employee::getSex, Collectors.groupingBy(e -> { if (e.getAge() < 35) { return "青年"; } else if (e.getSalary() < 50) { return "中年"; } else { return "老年"; } }))); System.out.println("多级分组结果:"); System.out.println(groupMap); //groupingBy+maxBy Map<String, Optional<Employee>> groupMaxMap = employees.stream() .collect(Collectors.groupingBy(Employee::getSex, Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)))); System.out.println("groupingBy + maxBy"); System.out.println(groupMaxMap); // groupingBy+counting 各个分组中的元素个数 Map<String, Long> groupCountMap = employees.stream() .collect( Collectors.groupingBy(Employee::getSex, Collectors.counting()) ); System.out.println("groupingBy + counting"); System.out.println(groupCountMap); //groupingBy+mapping Map<String, List<String>> groupMapinngMap = employees.stream() .collect( Collectors.groupingBy(Employee::getSex, Collectors.mapping(e -> { if (((Employee) e).getSalary() < 10000) { return "P5"; } else { return "P7"; } }, Collectors.toList())) ); System.out.println("groupingBy + mapping"); System.out.println(groupMapinngMap); }
运行结果:
多级分组结果: {女={老年=[Employee{name='王五', age=45, salary=20000.3, sex='女'}, Employee{name='赵六', age=37, salary=15000.5, sex='女'}]}, 男={青年=[Employee{name='张三', age=25, salary=5000.1, sex='男'}, Employee{name='李四', age=28, salary=8000.2, sex='男'}], 老年=[Employee{name='孙七', age=37, salary=18000.5, sex='男'}]}} groupingBy + maxBy {女=Optional[Employee{name='王五', age=45, salary=20000.3, sex='女'}], 男=Optional[Employee{name='孙七', age=37, salary=18000.5, sex='男'}]} groupingBy + counting {女=2, 男=3} groupingBy + mapping {女=[P7, P7], 男=[P5, P5, P7]}
-
元素分区
分区是分组的特殊情况,由一个 谓词(返回一个布尔值的函数)作为分类函数,称为分区函数。
通俗来讲就是现在的grouping 只能按true或者false进行分类方法 作用 partitioningBy 将集合分成两个区 partitioningBy+counting 返回每个分区的元素个数 @Test public void testPartition() { Map<Boolean, List<Employee>> partitionMap = employees.stream() .collect(Collectors.partitioningBy(e -> e.getSex().equals("男"))); System.out.println("partition:"); System.out.println(partitionMap); //partitioningBy + counting Map<Boolean, Long> partitionCountingMap = employees.stream() .collect(Collectors.partitioningBy(e -> e.getSex().equals("男"), Collectors.counting())); System.out.println("partition+counting"); System.out.println(partitionCountingMap); }
运行结果:
partition: {false=[Employee{name='王五', age=45, salary=20000.3, sex='女'}, Employee{name='赵六', age=37, salary=15000.5, sex='女'}], true=[Employee{name='张三', age=25, salary=5000.1, sex='男'}, Employee{name='李四', age=28, salary=8000.2, sex='男'}, Employee{name='孙七', age=37, salary=18000.5, sex='男'}]} partition+counting {false=2, true=3}
2.3. 强大的并行流
JAVA8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。其实JAVA8底层是使用JAVA7新加入的Fork/Join框架。
2.3.1. Fork/Join框架
采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
2.3.2. 并行流
并行流相对于自己实现的Fork/Join模型,在CPU利用率和运算速度上都显得更有优势。下面通过例子来实际对比一下。
计算1-100亿的运行速度:
-
常规方法:
public class NormalTest { public static void main(String[] args) { Instant start = Instant.now(); Long sum = 0L; for(long i = 1; i <= 10000000000L; i++){ sum += i; } Instant end = Instant.now(); long time = Duration.between(start, end).toMillis(); System.out.println("运行结果:" + sum + "\t运行时间:" + time); } }
运行结果:
运行结果:-5340232216128654848 运行时间:35861
-
Fork/Join模型:
public class ForkJoinTest extends RecursiveTask<Long> { private long start; private long end; //临界值 private final long THRESHHOLD = 1000L; public ForkJoinTest() { } public ForkJoinTest(long start, long end) { this.start = start; this.end = end; } /** * The main computation performed by this task. * * @return the result of the computation */ @Override protected Long compute() { // System.out.println(Thread.currentThread().getName()); if(end - start <= THRESHHOLD) { long sum = 0L; for (long i = start; i <= end; i++) { sum += i; } return sum; }else { long mid = (start + end) / 2; ForkJoinTest left = new ForkJoinTest(start, mid); left.fork(); //分支 ForkJoinTest right = new ForkJoinTest(mid + 1, end); right.fork(); //分支 return left.join() + right.join(); //合并 } } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public long getEnd() { return end; } public void setEnd(long end) { this.end = end; } public static void main(String[] args) { Instant start = Instant.now(); ForkJoinTask<Long> forkJoinTask = new ForkJoinTest(0L,10000000000L); ForkJoinPool forkJoinPool = new ForkJoinPool(); Long t = forkJoinPool.invoke(forkJoinTask); Instant end = Instant.now(); System.out.println("forkjoin运算结果:" + t + "\t耗费时间:" + Duration.between(start, end).toMillis()); } }
运行结果:
forkjoin运算结果:-5340232216128654848 耗费时间:10284
-
parallel
public class ParallelStreamTest { public static void main(String[] args) { Instant start = Instant.now(); OptionalLong sumOp = LongStream.range(1L, 10000000000L) .parallel() .reduce(Long::sum); Instant end = Instant.now(); System.out.println("Parallel运算结果:" + sumOp.getAsLong() + "\t运行时间:" + Duration.between(start, end).toMillis()); } }
运行结果:
Parallel运算结果:-5340232226128654848 运行时间:7703
2.4. 练习
private List<Transaction> transactions;
@Before
public void before() {
Trader tom = new Trader("Tom", "Beijing");
Trader jane = new Trader("Jane", "Shanghai");
Trader jim = new Trader("Jim", "Beijing");
Trader eric = new Trader("Eric", "Beijing");
transactions = Arrays.asList(
new Transaction(tom, 2018, 1000),
new Transaction(jane, 2019, 1200),
new Transaction(jane, 2017, 2000),
new Transaction(jim, 2018, 500),
new Transaction(eric, 2017, 4000),
new Transaction(eric, 2018, 600)
);
}
// 1. 找出2018年发生的所有交易,并按照交易额排序(从低到高)
// 2. 交易员都在哪些不同城市工作
// 3. 查找所有来自北京的交易员,并按照姓名排序
// 4. 返回所有交易员的姓名字符串,按字母排序
// 5. 有没有交易员是在上海工作的
// 6. 打印你生活在被北京的交易员的所有交易额
// 7. 所有交易中,最高的交易额是多少
// 8. 找到交易额最小的交易
4. 接口中的默认方法与静态方法
public interface MyTest {
boolean test(Employee employee);
default void deal(Employee employee){
System.out.println(employee );
}
}
当继承的父类和实现的接口中有相同签名的方法时,优先使用父类的方法。
当接口的父接口中也有同样的默认方法时,就近原则调用子接口的方法。
当实现的多个接口中有相同签名的方法时,必须在实现类中通过重写方法解决冲突问题,否者无法通过编译,在重写的方法中可以通过 接口名.super.方法名(); 的方式显示调用需要的方法。
5. 新时间日期API
5.1. 旧的时间日期api缺陷
Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且都不是线程安全的。
-
Date如果不格式化,打印出的日期可读性差。
Wed Feb 05 00:31:12 CST 2020
可以使用 SimpleDateFormat 对时间进行格式化,但 SimpleDateFormat 是线程不安全的。
Date对时间处理比较麻烦,比如想获取某年、某月、某星期,以及 n 天以后的时间,如果用Date来处理的话真是太难了,并且 Date 类的 getYear、getMonth 这些方法都被弃用了。
SimpleDateFormat线程安全的使用方法:
- 避免线程之间共享一个 SimpleDateFormat 对象,每个线程使用时都创建一次 SimpleDateFormat 对象 => 创建和销毁对象的开销大
- 对使用 format 和 parse 方法的地方进行加锁 => 线程阻塞性能差
- 使用 ThreadLocal 保证每个线程最多只创建一次 SimpleDateFormat 对象 => 较好的方法
5.2. Java8提供的日期时间API
Java 8的日期和时间类包含 LocalDate、LocalTime、Instant、Duration 以及 Period,这些类都包含在 java.time 包中,Java 8 新的时间API的使用方式,包括创建、格式化、解析、计算、修改。
5.2.1. 基础类库
-
使用LocalDate LocalTime LocalDateTime
LocalDate、LocalTime、LocalDateTime类的实例都是不可变对象(参考String),分别表示使用ISO-8601日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息,也不包含于时区相关的信息。
@Test public void test1() { LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); LocalDateTime localDateTime1 = LocalDateTime.of(2016, 11, 12, 14, 18, 28); System.out.println(localDateTime1); LocalDateTime localDateTime2 = localDateTime1.plusYears(1L); System.out.println(localDateTime2); LocalDateTime localDateTime3 = localDateTime1.minusYears(2L); System.out.println(localDateTime3); System.out.println(localDateTime1.getYear()); }
-
Instant 时间戳(以UNIX元年:1970-01-01 00:00:00到当前时间的毫秒值),默认获取的是UTC时区。
@Test public void testInstant() { Instant ins1 = Instant.now(); System.out.println(ins1); OffsetDateTime odt = ins1.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); System.out.println(ins1.toEpochMilli()); }
运行结果:
2020-02-04T16:58:19.005Z 2020-02-05T00:58:19.005+08:00 1580835499005
-
Duration计算两个时间年之间的间隔,Period计算两个日期之间的间隔。
@Test public void testDur() throws InterruptedException { Instant instant1 = Instant.now(); Thread.sleep(1000); Instant instant2 = Instant.now(); Duration duration = Duration.between(instant1, instant2); System.out.println(duration.toMillis()); LocalDate ld2 = LocalDate.now(); LocalDate ld1 = LocalDate.of(2018,11, 12); Period period = Period.between(ld1, ld2); System.out.println(period); }
5.2.2. 时间校正器
TemporalAdjuster: 时间校正器。可以通过它获得诸如:下一个周日等操作。
-
TemporalAdjusters: 该类通过静态方法提供了大量的常用的TemporalAdjuster的实现。
@Test public void test3() { LocalDate now = LocalDate.now(); LocalDate nextSunday = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); System.out.println("下一个周日:" + nextSunday); LocalDate nextWorkDay = now.with(l -> { LocalDate ldt = (LocalDate) l; DayOfWeek dayOfWeek = ldt.getDayOfWeek(); if (dayOfWeek.equals(DayOfWeek.FRIDAY)) { return ldt.plusDays(3); } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) { return ldt.plusDays(2); } else { return ldt.plusDays(1); } }); System.out.println("下一个工作日:" + nextWorkDay); }
运行结果:
下一个周日:2020-02-09 下一个工作日:2020-02-06 Process finished with exit code 0
5.2.3. 日期时间格式化
DateTimeFomatter格式化时间/日期
@Test
public void test4() {
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("ISO_DATE_TIME" + isoDateTime.format(localDateTime));
DateTimeFormatter cnDate = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String dateStr = cnDate.format(localDateTime);
System.out.println("自定义" + dateStr);
LocalDateTime nowDate = LocalDateTime.parse(dateStr, cnDate);
System.out.println("字符串转日期:" + nowDate);
}
运行结果
ISO_DATE_TIME2020-02-05T10:36:38.122
自定义2020年02月05日 10时36分38秒
字符串转日期:2020-02-05T10:36:38
Process finished with exit code 0
5.2.4. 时区处理
java8中加入了对市区的支持,带时区的时间分别为
ZonedDate
、ZonedTime
、ZonedDateTime
。其中每个时区都对应着ID,地区ID都为"{区域}/{城市}"的格式,例如Asia/Shanghai
等。-
ZoneId
:该类中包含了所有的时区信息,getAvailableZoneIds()
: 可以获取所有时区信息of(id)
: 用指定的时区信息获取ZoneId对象
@Test
public void test5() {
ZoneId.getAvailableZoneIds().forEach(System.out::println);
}
@Test
public void test6() {
LocalDateTime amDate = LocalDateTime.now(ZoneId.of("Antarctica/Macquarie"));
System.out.println("LocalDateTime" + amDate);
ZonedDateTime zdt = amDate.atZone(ZoneId.of("Antarctica/Macquarie"));
System.out.println("zonedDateTime: " + zdt);
}
LocalDateTime2020-02-05T13:49:23.826
zonedDateTime: 2020-02-05T13:49:23.826+11:00[Antarctica/Macquarie]
Process finished with exit code 0
6. 其他新特性
java8对注解也提供了两点改进:
6.1. 可重复注解
/**
* @Description: java类作用描述
* @Author: zlzhou
* @CreateDate: 2020/2/5
*/
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "Hello";
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
@MyAnnotation("你好")
@MyAnnotation("世界")
public class MyClass {
public static void main(String[] args) {
Class<MyClass> clazz = MyClass.class;
MyAnnotation[] annotations = clazz.getAnnotationsByType(MyAnnotation.class);
if(annotations != null){
Arrays.stream(annotations)
.map(a -> a.value())
.forEach(System.out::println);
}
}
}
6.2. 类型的注解
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),可以在编译的时候检测出runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error),以提高代码质量。这就是类型注解的作用。
使用Checker Framework可以找到类型注解出现的地方并检查。
-
增加环境变量:
export CHECKERFRAMEWORK=${你的安装路径}/checker-framework-2.1.9 alias javacheck='$CHECKERFRAMEWORK/checker/bin/javac'
-
然后就可以用一个简单的
javacheck
运行CF啦javacheck -processor org.checkerframework.checker.nullness.NullnessChecker GetStarted.java
这条指令中的
-process org.checkerframework.checker.nullness.NullnessChecker
制定了需要检测的错误是空指针,CF还自带了许多其他有用的插件检测其他类型错误。 -
GetStarted.java
import org.checkerframework.checker.nullness.qual.*; public class GetStarted { void sample() { @NonNull Object ref = null; } }
-
编译结果:
GetStarted.java:5: 错误: [assignment.type.incompatible] incompatible types in assignment. @NonNull Object ref = null; ^ found : null required: @UnknownInitialization @NonNull Object 1 个错误