Java8新特性主要内容
这篇文章主要是根据尚硅谷的课程《java8新特性》来的,加入一些自己的理解编写完成,建议先去看一下视频熟悉一下,本次主要讲的java8的特性有下面这么几个:
Lambda
表达式- 函数式接口
- 方法引用于构造器引用
Stream API
- 接口中的默认方法与静态方法
- 新时间日期API
- 其他新特性
java8较之前的版本来讲,速度更块,代码更少(引入了新的语法Lambda表达式)、更加强大的Stream api、便于并行、最大化减少空指针异常(Optional)、其中最大的核心就是Lambda和Stream API
Lambda表达式
什么是Lambda表达式
- Lambda表达式时一个
匿名函数
,我们可以把Lambda表达式理解为一段可以传递的代码
(将代码想数据一样进行传递),可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使java的语言表达能力得到了提升如果我们有一个需求,对员工按照一定的规则进行排序:public static void main(String[] args) {
//匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
//comparator就可以直接拿来用
//使用lambda简化代码
Comparator<Integer> comparator1 = (x,y)->Integer.compare(x,y);
} - 当排序一定的时候我们可以通过循环员工List找出符合条件的即可,这样每次针对一个排序就新增一个函数,非常不好;
public static List<Employee> filterEmpByAge(List<Employee> employees){
List<Employee> list = new ArrayList<>();
for (Employee e : employees) {
if(e.getAge()>35){
list.add(e);
}
}
return list;
} - 如果条件过多,最好是将过滤方法抽象成接口,针对不同的规则返回查询方式,然后再根据查询方式过滤list;(策略设计模式),这样如果是简单的小功能容易产生没有必要的代码量,不适用。
//提供一个统一的过滤接口
public interface FilterEmpInter<T> {
public boolean filter(T t);
}
//提供一个执行逻辑类
public class FilterEmpByAge implements FilterEmpInter<Employee> {
public boolean filter(Employee employee) {
return employee.getAge()>35;
}
}
//编写一个统一函数
public static List<Employee> filterEmpByAge2(List<Employee> employees,FilterEmpInter filterEmpInter){
List<Employee> list = new ArrayList<>();
for (Employee e : employees) {
if(filterEmpInter.filter(e)){
list.add(e);
}
}
return list;
}
//调用执行
List<Employee> list = filterEmpByAge2(employees,new FilterEmpByAge());
for (Employee e :list) {
System.out.println(e);
} - 使用匿名内部类,这样减少了新增一个类的开销,代码不会太臃肿
public static List<Employee> filterEmp(List<Employee> employees){
return filterEmpByAge2(employees, new FilterEmpInter<Employee>() {//匿名内部类
public boolean filter(Employee o) {
return o.getAge()>35;
}
});
} - 使用lambda来简化代码
List<Employee> list = filterEmpByAge2(employees,(FilterEmpInter<Employee>) e->e.getAge()<35); //输出35岁以下的员工,lambda表达式方式
list.forEach(System.out::println);//输出 - 不依赖接口的lambda+Stream API
employees.stream()
.filter(e->e.getAge()<35)
.forEach(System.out::println); //使用stream可以直接对employee进行操作。
Lambda基础语法
Java8中引入了一个新的操作符->
,叫做箭头操作符或者lambda操作符。->
操作符将lambda表达式分成了两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所需功能,即Lambda体
Lambda的语法格式
无参无返回值的Lambda语法格式:
()->{函数体}
public class LambdaTest1 {
public static void main(String[] args) {
int num = 0;//jdk1.7匿名内部类调用同级局部变量需要变量为final,1.8则不需要
//runnable和runnable1是一样的
Runnable runnable = new Runnable() {
public void run() {
System.out.println("hello"+num);//num不能修改,因为本质还是final
}
};
runnable.run();
Runnable runnable1 = ()-> System.out.println("hello");
runnable.run();
}
}有一个参数无返回值的lambda语法格式:
(x)->{函数体}
或x->{函数体}
//Consumer类就是一个准备好的一个输入的函数式接口
Consumer<String> consumer = (x)-> System.out.println(x);
consumer.accept("你好");有多个参数并且lambda有多个语句,且有返回值
Comparator<Integer> comparator = (x,y)->{
System.out.println("进入函数内部");
return Integer.compare(x,y);
};
comparator.compare(1,2);若Lambda体中只有一条语句,return和大括号都可以省略不写
Lambda的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即
类型推断
函数式接口
Lambda需要函数式接口的支撑。函数式接口
:接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface
修饰一下,@FunctionalInterface
作用是检查该接口是否是函数式接口。
如果接口被@FunctionalInterface
修饰,那么这个接口必须是函数式接口,如果接口有其他方法编译会报错。
java内置四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer |
T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier |
无 | T | 返回类型为T的对象,包含方法:T get(); |
Function<T,R>函数型接口 | T | R | 对类型为T的对象应用操作,返回接口,结果为R类型的对象,包含方法:R apply(T t) |
Predicate |
T | boolean | 确定类型为T的对象是否满足某约束,并且返回boolean值。包含了方法boolean test(T t) |
Consumer<T>
消费型接口例子,有一个参数但是没有返回值,属于只负责消费的接口public static void main(String[] args) {
//
test(5,(x)-> System.out.println(x));
}
//创建一个带有
public static void test(int i,Consumer<Integer > consumer){
consumer.accept(i);
}Supplier<T>
供给型接口例子public static void main(String[] args) {
//
getNumberList(5,()-> (int)(Math.random()*100));
}
//需求,产生指定个数的整数,并放入集合中去
public static List<Integer> getNumberList(int num, Supplier<Integer> supplier){
List<Integer> list = new ArrayList<>();
for(int i=1;i<num+1;i++){
list.add(supplier.get()); //至于产生什么样的数字,交给lambda
}
return list;
}Function<T,R>
函数型接口例子:public static void main(String[] args) {
String newstr = strHandler("\t\t\t zenshin ",(str)->str.trim());
System.out.println(newstr);
}
public static String strHandler(String str, Function<String,String> function){
return function.apply(str);
}Predicate<T>
断言型接口例子:public static void main(String[] args) {
List<String> list = Arrays.asList("hello","zenshin","lambda","www","ok");
List<String> stringList = filerStr(list, (s) -> s.length() > 3);//大于3的输出
stringList.forEach(System.out::println);
}
public static List<String> filerStr(List<String> list, Predicate<String> pre){
List<String> stringList = new ArrayList<>();
for (String str: list) {
if(pre.test(str)){ //这里使用断言
stringList.add(str);
}
}
return stringList;
}
扩展接口
基于上面四种基础接口,可以扩展一系列接口,方便开发者使用。
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction |
T,U | R | 对类型为T,U的对象应用操作,返回R类型得结果,包含方法:R apply(T t,U u); |
UnaryOperator |
T | T | 对类型为T的对象进行一元运算,并返回T类型的接口。包含方法为T apply(T t) |
BinaryOperator |
T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(T t1,T t2) |
BiConsumer<T,U> | T,U | void | 对类型为T,U参数应用操作,包含方法为void accept(T t,U u) |
ToIntFunction |
T | int.long,doule | 分别计算int、long、doule值的函数 |
IntFunction |
int、long、doule | R | 参数分别为int、long、doule类型的函数 |
使用方法与基础接口一样。
方法引用和构造器引用
方法引用
若Lambda体中的内容有方法已经实现了,我们可以使用”方法引用”(可以理解为方法引用是Lambda表达式的另一种表现形式)
主要有三种语法格式:
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名:当第一个参数是实例方法的调用者,另一个参数是实例方法的参数时可以使用。注意:
public class FunctionTest {
public static void main(String[] args) {
//比较两个字符串是否相等
//类::实例方法
BiPredicate<String,String> biPredicate = (x,y)->x.equals(y);
BiPredicate<String,String> biPredicate1 = String::equals;
Consumer<String> consumer = (x)-> System.out.println(x);
Consumer<String> consumer1 = System.out::println;
//对象::实例方法名
PrintStream out = System.out;
Consumer<String> consumer2 = out::println;
//注意:这里使用的方法引用的方法签名要与函数式接口的签名一致
}
} - lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
- Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
构造器引用
格式为ClassName::new
public class FunctionTest { |
- 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
数组引用:
格式为Type[]::new
public class FunctionTest { |
强大的Stream API
Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据方式。
Stream(流)到底是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。”集合讲的是数据,流讲的是计算”
- Stream自己不会存储元素
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
- Stream 操作时延迟执行的。意味着他们会等到需要结果的时候才执行
Stream的操作三个步骤
- 创建Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
创建Stream
- 通过
Collection
系列集合提供的stream()
或者parallelStream()
- default Stream
stream() : 返回一个顺序流 - default Stream
parallelStream() : 返回一个并行流 List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
- default Stream
- 通过Arrays中的静态方法stream()获取数组流
String[] strings = new String[10];
Stream<String> stream1 = Arrays.stream(strings); - 通过Stream类中的静态方法of()
Stream<String> stream2 = Stream.of("123","23","3");
- 通过Stream类中的静态方法创建无限流Stream流只有在真正运算的时候才会去执行
//迭代的方式
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
//生成的方式
Stream<Integer> stream4 = Stream.generate(() -> (int) Math.random());
Stream中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理!而在终止操作时一次性全部处理,成为”惰性求值”
筛选和切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收lambda,从流中排除某些元素 |
distinct() | 筛选,通过流所生成元素得hashCode()和equals()去除重复元素 |
limit(long masSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n个元素得流。若流中元素不足n个,则返回一个空流。与limit(n)互补 |
filter
过滤Stream<Employee> stream = employees.stream().filter((employee -> employee.getAge() > 35));
//必须要有终止操作,不然是不会有任何结果得
stream.forEach(System.out::println);limit
限制employees.stream()
.filter((employee -> employee.getAge() > 35))
//当filter返回前两个符合条件的时候,就不向下遍历了,减少性能损耗
.limit(2)
.forEach(System.out::println);skip
employees.stream()
.filter((employee -> employee.getAge() > 35))
//取后两个,与limit互补
.skip(2)
.forEach(System.out::println);distinct
去重//根据hashcode和equals进行去重,需要重写hascode和equals才能生效
employees.stream()
.distinct()
.forEach(System.out::println);
映射
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为一个参数,该函数会被应用到每个元素上,并将其映射为一个新得元素 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为一个参数,该函数会被应用到每个元素上,并将其映射为一个新得DoubleStream |
mapToInt(ToIntFunction f) | 接收一个函数作为一个参数,该函数会被应用到每个元素上,并将其映射为一个新得IntStream |
mapToLong(ToLongFunction f) | 接收一个函数作为一个参数,该函数会被应用到每个元素上,并将其映射为一个新得LongSTream |
flatMap(Function f) | 接收一个函数作为一个参数,将流中每个值都换成另一个流,然后把所有流连接成一个流 |
map
映射//将所有的字母转大写
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
list.stream()
.map(String::toUpperCase)
.forEach(System.out::println);flatMap
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
//flatMap作用是将流里的流转换成一个连续的流
list.stream().flatMap((str)->{
List<Character> characters = new ArrayList<>();
for (Character c:str.toCharArray()) {
characters.add(c);
}
//将str转换成一个流
return characters.stream();
//flatMap就将每个循环出来的流变成一个连续的流输出
}).forEach(System.out::println);
排序
方法 | 描述 |
---|---|
sorted() | 产生一个新流,其中按照自然顺序排序 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
按照Comparable
排序就是自然排序,按照Comparator
排序就是定制排序
Stream的终止操作
终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void;
查找与匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
count() | 返回当前流中的总个数 |
max() | 返回流中的最大值 |
min() | 返回流中的最小值 |
allMatch
,检查是否都符合条件,传入的是一个断言函数性接口,只要全部符合条件才返回true.anyMatch
、noneMatch
使用方法类似 boolean match = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(match);
boolean match = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(match);//false
boolean match = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(match);//true
boolean match = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(match);//false,有存在的就返回falsefindFirst
返回第一个元素,这个返回的是Optional
容器类Optional<Employee> first = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
Employee employee = first.get();count
、max
、min
使用方法Long count = employees.stream().count();//获取数量
Optional<Employee> max = employees.stream().max(Comparator.comparingDouble(Employee::getSalary));//获取工资最大值
Optional<Float> min = employees.stream().map(Employee::getSalary).min(Double::compare);//获取工资最小值的金额
归约和收集
reduce(T identity,BinaryOperator<T> accumulator)/reduce(BinaryOperator<T> accumulator)
,可以将流中元素反复结合起来,得到一个值
//对集合做一个累加 |
通常map
和reduce
一起使用,先进行映射,然后将映射后的值进行累计。map-reduce
模式被广泛的应用。
- 收集
collect
–将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法Collector
接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map).但是Collectors
实用类提供了很多静态方法,可以方便地创建常见收集器实例。
//将名字收集为一个list |
并行流与顺序流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明式地通过parallel()
与sequential()
在并行流与顺序流之间进行切换
了解Fork/Join框架
Fork/Join框架
:就是在必要地情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不能再拆时),将一个个的小任务运算的结果进行join汇总。
Fork/Join框架与传统线程池的区别
Fork/Join采用”工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中,就是说当有多个队列在执行任务的时候,执行快的线程队列会偷执行慢的线程队列的任务加到自己的任务中,这样能让线程队列达到最高效的使用。
Fork/Join框架的使用
首先创建一个类
ForkJoinCalculate
继承RecursiveTask<Long>
,实现compute()
方法RecursiveAction
:不带返回值的任务RecursiveTask
:带返回值的任务- 拆分任务本质是一个递归,和递归实现类似
public class ForkJoinCalculate extends RecursiveTask<Long> {
private long start ;
private long end;
//临界条件
private static final long THRESHOLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
//递归
protected Long compute() {
long length = end-start;
if(length<=THRESHOLD){
long sum = 0;
for (long i = start;i<=end;i++){
sum+=i;
}
return sum;
}else {
long middle = (start+end)/2;
ForkJoinCalculate left = new ForkJoinCalculate(start,middle);
left.fork();//拆分子任务,压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle+1,end);
right.fork();
//将任务合并
return right.join()+left.join();
}
}
}
使用:
- 首先
ForkJoinPool
创建一个线程池 - 定义一个task,加入队列执行
- 首先
//java8时间计算api |
- java8以后进行Fork/Join框架计算
//串行流 sequential
Instant start = Instant.now();
long reduce = LongStream.rangeClosed(0, 100000000000L).reduce(0, Long::sum);
System.out.println(reduce);
Instant end = Instant.now();
System.out.println("串行流耗时:"+ Duration.between(start,end).toMillis())
//并行流 parallel
Instant start1 = Instant.now();
long reduce1 = LongStream.rangeClosed(0, 100000000000L).parallel().reduce(0, Long::sum);
System.out.println(reduce1);
Instant end1 = Instant.now();
System.out.println("并行流耗时:"+ Duration.between(start1,end1).toMillis());
Optional类
Optional
常用方法
- 创建:
- Optional.of(T t):创建一个Optional实例
- Optional.empty():创建一个空的Optional实例
- Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
- 获取:
- get():获取optional实例中的对象,当optional 容器为空时报错
- 判断:
- isPresent():判断optional是否为空,如果空则返回false,否则返回true
- ifPresent(Consumer c):如果optional不为空,则将optional中的对象传给Comsumer函数
- orElse(T other):如果optional不为空,则返回optional中的对象;如果为null,则返回 other 这个默认值
- orElseGet(Supplier
other):如果optional不为空,则返回optional中的对象;如果为null,则使用Supplier函数生成默认值other - orElseThrow(Supplier
exception):如果optional不为空,则返回optional中的对象;如果为null,则抛出Supplier函数生成的异常
- 过滤:
- filter(Predicate
p):如果optional不为空,则执行断言函数p,如果p的结果为true,则返回原本的optional,否则返回空的optional
- filter(Predicate
- 映射:
- map(Function<T, U> mapper):如果optional不为空,则将optional中的对象 t 映射成另外一个对象 u,并将 u 存放到一个新的optional容器中。
- flatMap(Function< T,Optional> mapper):跟上面一样,在optional不为空的情况下,将对象t映射成另外一个optional
- 区别:map会自动将u放到optional中,而flatMap则需要手动给u创建一个optiona
Demo应用
需求: 学校想从一批学生中,选出年龄大于等于18,参加过考试并且成绩大于80的人去参加比赛。
数据准备:
public class Student {
private String name;
private int age;
private Integer score;
//省略 construct get set
}
public List<Student> initData(){
Student s1 = new Student("张三", 19, 80);
Student s2 = new Student("李四", 19, 50);
Student s3 = new Student("王五", 23, null);
Student s4 = new Student("赵六", 16, 90);
Student s5 = new Student("钱七", 18, 99);
Student s6 = new Student("孙八", 20, 40);
Student s7 = new Student("吴九", 21, 88);
return Arrays.asList(s1, s2, s3, s4, s5, s6, s7);
}java8 之前写法:
public void beforeJava8() {
List<Student> studentList = initData();
for (Student student : studentList) {
if (student != null) {
if (student.getAge() >= 18) {
Integer score = student.getScore();
if (score != null && score > 80) {
System.out.println("入选:" + student.getName());
}
}
}
}
}java8 写法:
public void useJava8() {
List<Student> studentList = initData();
for (Student student : studentList) {
Optional<Student> studentOptional = Optional.of(student);
Integer score = studentOptional.filter(s -> s.getAge() >= 18).map(Student::getScore).orElse(0);
if (score > 80) {
System.out.println("入选:" + student.getName());
}
}
}
接口中的默认方法与静态方法
java8中允许接口中包含具有具体实现的方法,该方法称为”默认方法”,默认方法使用default
关键字修饰
interface myFun<T>{ |
接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法被忽略
- 接口冲突。如果一个父接口提供了一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
java8支持接口中静态方法
java8允许在接口中定义静态方法,但是个人感觉意义不大。
新时间日期API
过去的时间日期api存在线程安全问题,下面进行一个举例:
我们使用多线程的方式去解析同一个日期时间
public class TestSimpleDateFormat {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//时间格式化器
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
//创建线程
Callable<Date> callable = () -> dateFormat.parse("20161218");
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//创建返回集合
List<Future<Date>> futures = new ArrayList<>();
for (int i=0;i<10;i++){
futures.add(pool.submit(callable));
}
for (Future<Date> future:futures){
System.out.println(future.get());
}
//关闭线程池
pool.shutdown();
}
}
//这样运行会报错,因为多线程访问的时候会造成SimpleDateFormat解析失败我们进行改造,将
dateFormat
进行线程保护
首先我们创建一个DateFormatThreadLocal
类,用于锁住变量public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date convert(String source) throws ParseException {
return df.get().parse(source);
}
}改造原有方法,去掉原有的
SimpleDateFormat
,从公共变量取,这样就可以解析成功了public class TestSimpleDateFormat {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程
Callable<Date> callable = () -> DateFormatThreadLocal.convert("20161218");
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//创建返回集合
List<Future<Date>> futures = new ArrayList<>();
for (int i=0;i<10;i++){
futures.add(pool.submit(callable));
}
for (Future<Date> future:futures){
System.out.println(future.get());
}
//关闭线程池
pool.shutdown();
}
}也可以为变量加一把锁
//创建线程
Callable<Date> callable = new Callable<Date>() {
public Date call() throws Exception {
synchronized (dateFormat){
return dateFormat.parse("20160203");
}
}
};引入新的时间api后这种无需自己加锁
public class TestSimpleDateFormat {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//时间格式化器
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd");
//创建线程
Callable<LocalDate> callable = ()->LocalDate.parse("20201123",dateFormat);
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//创建返回集合
List<Future<LocalDate>> futures = new ArrayList<>();
for (int i=0;i<10;i++){
futures.add(pool.submit(callable));
}
for (Future<LocalDate> future:futures){
System.out.println(future.get());
}
pool.shutdown();
}
}
新的时间API介绍
使用LocalDate、LocalTime、LoaclDateTime
LocalDate
、LocalTime
、LoaclDateTime
类的实例时不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。可不包含与时区相关的信息。
它们三个的使用方式是相同的,这种时间格式是用于给人们看的,是人能解读的格式
//1、LocalDate、LocalDate、LocalDateTime |
Instant时间戳
时间戳指的是以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值。
|
计算时间间隔
Duration
:计算两个”时间”之间的间隔
public void test3() throws InterruptedException {
Instant instant = Instant.now();
Thread.sleep(1000);
Instant instant1 = Instant.now();
Duration between = Duration.between(instant, instant1);
System.out.println(between.getSeconds());//获取秒
System.out.println(between.toMillis());//获取毫秒
LocalTime localTime = LocalTime.now();
Thread.sleep(1000);
LocalTime localTime2 = LocalTime.now();
System.out.println(Duration.between(localTime,localTime2).toMillis());
}Period
:计算两个”日期”之间的间隔
public void test4(){
LocalDate localDate = LocalDate.of(2015,1,2);
LocalDate localDate1 = LocalDate.now();
Period between = Period.between(localDate, localDate1);
System.out.println(between);//P5Y8M26D ISO格式
System.out.println(between.getYears());
System.out.println(between.getMonths());
System.out.println(between.getDays());
}
时间矫正器
TemporalAdjuster
:时间矫正器。有时我们可能需要获取例如:将日期调整到”下个周日”等操作TemporalAdjusters
: 该类通过静态方法提供了大量的常用TemporalAdjuster
的实现
public void test5(){
LocalDateTime localDateTime = LocalDateTime.now();
//2020-09-28T11:44:15.729
System.out.println(localDateTime);
LocalDateTime with = localDateTime.with(TemporalAdjusters.firstDayOfMonth());
//2020-09-01T11:44:15.729
System.out.println(with);
//自定义时间:下一个工作日
LocalDateTime with1 = localDateTime.with((l) -> {
LocalDateTime localDateTime1 = (LocalDateTime) l;
DayOfWeek dayOfWeek = localDateTime1.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
return localDateTime1.plusDays(3);
} else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDateTime1.plusDays(2);
} else {
return localDateTime1.plusDays(1);
}
});
//2020-09-29T11:51:35.533
System.out.println(with1);
}
时间日期格式化
DateTimeFormatter
:格式化时间日期
|
时区的处理
java8加入了对时区的支持,带时区的时间分别为:ZonedDate
,ZonedTime
,ZonedDateTime
,其中每个时区都对应着ID,地区ID都为”{区域}/{城市}”的格式,例如:Asia/Shanghai等
Zoneid:该类中包含了所有的时区信息
- getAvailableZoneLds():可以获取所有时区信息
- of(id):用于指定的时区信息获取ZoneId对象
public void test7(){
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
//所有的时区
availableZoneIds.forEach(System.out::println);
//创建时候指定时区
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
//2020-09-28T13:10:50.369
System.out.println(now);
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Tokyo"));
//2020-09-28T12:13:37.732+09:00[Asia/Tokyo]
System.out.println(zonedDateTime);
}
可重复注解与类型注解
重复注解:java在java8之前是不支持重复注解得,现在支持重复注解,实现方式如下:
定义一个注解
(RetentionPolicy.RUNTIME)
(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public MyAnnotation {
String value() default "";
}定义一个容器注解
(RetentionPolicy.RUNTIME)
(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public MyAnnotations {
MyAnnotation[] value();
}在
MyAnnotation
注解上增加@Repeatable(MyAnnotations.class)
使用样例
public class TestAnnotation {
"Hello") (
"World") (
public void show(){
}
//使用
public void test1() throws NoSuchMethodException {
Class<TestAnnotation> testAnnotationClass = TestAnnotation.class;
Method m1 = testAnnotationClass.getMethod("show");
MyAnnotation[] byType = m1.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : byType){
System.out.println(myAnnotation.value());
}
}
}
类型注解
类型注解需要在Target
注解里面加入TYPE_PARAMETER
类型,就可以实现对方法参数进行注解修饰