avatar

java8新特性

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>() {
    @Override
    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> {
    @Override
    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>() {//匿名内部类
    @Override
    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() {
    @Override
    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(Function子接口) T T 对类型为T的对象进行一元运算,并返回T类型的接口。包含方法为T apply(T t)
BinaryOperator(BiFunction子接口) 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,TolongFunction,ToDouleFunction T int.long,doule 分别计算int、long、doule值的函数
IntFunction、LongFunction、DouleFunction 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 {
public static void main(String[] args) {
//构造器引用
Supplier<Employee> supplier = ()->new Employee();
Supplier<Employee> supplier1 = Employee::new;//这里是无参构造器调用创造对象

Function<Integer,Employee> function = (x)->new Employee(x);
Function<Integer,Employee> function1 = Employee::new;//调用只有id的有参构造器
}
}
  • 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致

数组引用:

格式为Type[]::new

public class FunctionTest {
public static void main(String[] args) {
//其实还是构造器引用
Function<Integer,String[]> function = (x)->new String[x];
Function<Integer,String[]> function1 = String[]::new;
}
}

强大的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();
  • 通过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<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
    //生成的方式
    Stream<Integer> stream4 = Stream.generate(() -> (int) Math.random());
    Stream流只有在真正运算的时候才会去执行

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.anyMatchnoneMatch使用方法类似 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,有存在的就返回false
  • findFirst返回第一个元素,这个返回的是Optional容器类

    Optional<Employee> first = employees.stream()
    .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
    .findFirst();
    Employee employee = first.get();
  • countmaxmin使用方法

    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),可以将流中元素反复结合起来,得到一个值
//对集合做一个累加
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8);
//0作为一个初始值,一个种子
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce); //36

//没有种子值的返回一个容器类,因为可能为空
Optional<Float> optional = employees.stream().map(Employee::getSalary).reduce(Float::sum);//计算工资总和

通常mapreduce一起使用,先进行映射,然后将映射后的值进行累计。map-reduce模式被广泛的应用。

  • 收集collect–将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
    Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map).但是Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例。
//将名字收集为一个list
List<String> collect = employees.stream().map(Employee::getName).collect(Collectors.toList());
//如果需要特性地收集器,我们可以使用Collectors.toCollection()
HashSet<String> collect = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));

Long collect = employees.stream().collect(Collectors.counting());//总数

employees.stream().collect(Collectors.averagingDouble(Employee::getAge));//年龄平均值

employees.stream().collect(Collectors.summingDouble(Employee::getSalary));//工资总额

employees.stream().collect(Collectors.toMap(Employee::getAge,Employee::getId));//年龄和id的map

//按照名称进行分组
Map<Integer, List<Employee>> collect = employees.stream().collect(Collectors.groupingBy(Employee::getAge));

//多级分组 groupingBy后再groupingBy
Map<Integer, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getAge, Collectors.groupingBy(Employee::getName)));

//分区,满足条件的一个区,不满足的一个区
Map<Boolean, List<Employee>> map1 = employees.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 4000));

//另一种获取方式
DoubleSummaryStatistics collect = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
collect.getAverage();
collect.getCount();
collect.getMax();

//连接字符串
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining());
//中间加入分隔符
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(","));
//加入前缀,加入后缀
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(",","(",")"));

并行流与顺序流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
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;
      }
      //递归
      @Override
      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
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinCalculate(0,100000000000L);
Long invoke = pool.invoke(task);
System.out.println(invoke);
Instant end = Instant.now()
System.out.println("耗时:"+ Duration.between(start,end).toMillis());//16178
  • 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 类(java.util.Optional)是一个容器类,代表一个值存在或者不存在,原来用null表示一个值不存在,现在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
  • 映射:
    • 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 之前写法:

    @Test
    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 写法:

    @Test
    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>{
T func(int a);

default String getName(){
return "hello world";
}
}

接口默认方法的”类优先”原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法被忽略
  • 接口冲突。如果一个父接口提供了一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

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>(){
    @Override
    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>() {
    @Override
    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

LocalDateLocalTimeLoaclDateTime类的实例时不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。可不包含与时区相关的信息。
它们三个的使用方式是相同的,这种时间格式是用于给人们看的,是人能解读的格式

//1、LocalDate、LocalDate、LocalDateTime
@Test
public void test1(){
//获取系统当前时间
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//获取指定日期时间
LocalDateTime of = LocalDateTime.of(2015, 10, 19, 13, 22, 33);
System.out.println(of);
//时间计算操作,每一次操作都会返回一个新的实例
LocalDateTime localDateTime1 = localDateTime.plusDays(2);
System.out.println(localDateTime1);
LocalDateTime localDateTime2 = localDateTime.minusDays(2);
System.out.println(localDateTime2);
}

Instant时间戳

时间戳指的是以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值。

@Test
public void test2(){
//默认获取UTC时区时间
Instant instant = Instant.now();
System.out.println(instant); //2020-09-28T03:19:52.163Z
//毫秒时间戳
System.out.println(instant.toEpochMilli()); //1601263256122
//增加偏移量 8小时
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); //2020-09-28T11:19:52.163+08:00
}

计算时间间隔

  • Duration:计算两个”时间”之间的间隔
    @Test
    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:计算两个”日期”之间的间隔
    @Test
    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的实现
    @Test
    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:格式化时间日期

@Test
public void test6(){
DateTimeFormatter dateTime = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String str = localDateTime.format(dateTime);
//2020-09-28
System.out.println(str);

//自定义
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String str2 = localDateTime.format(dateTimeFormatter);//或者dateTimeFormatter.format(localDateTime)
//2020年09月28日 11:59:00
System.out.println(str2);

//解析字符串到日期
LocalDateTime newdate = LocalDateTime.parse(str2,dateTimeFormatter);
System.out.println(newdate);
}

时区的处理

java8加入了对时区的支持,带时区的时间分别为:ZonedDate,ZonedTime,ZonedDateTime,其中每个时区都对应着ID,地区ID都为”{区域}/{城市}”的格式,例如:Asia/Shanghai等
Zoneid:该类中包含了所有的时区信息

  • getAvailableZoneLds():可以获取所有时区信息
  • of(id):用于指定的时区信息获取ZoneId对象
    @Test
    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之前是不支持重复注解得,现在支持重复注解,实现方式如下:

    • 定义一个注解

      @Retention(RetentionPolicy.RUNTIME)
      @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
      public @interface MyAnnotation {
      String value() default "";
      }
    • 定义一个容器注解

      @Retention(RetentionPolicy.RUNTIME)
      @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
      public @interface MyAnnotations {
      MyAnnotation[] value();
      }
    • MyAnnotation注解上增加@Repeatable(MyAnnotations.class)

    • 使用样例

      public class TestAnnotation {
      @MyAnnotation("Hello")
      @MyAnnotation("World")
      public void show(){

      }
      //使用
      @Test
      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类型,就可以实现对方法参数进行注解修饰

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/08/21/java8Newfeature/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论