Java8 新特性及开发技巧

0、java新特性简介

  • 速度更快

    1. 数据结构发生变化

    2. jvm内存模型变化 新生代、老年代、永久区、方法计数器、栈

    3. 新的垃圾回收机制

  • 代码更少

  • 强大的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体

语法格式

  1. 无参数,无返回值 比如 Runable接口

    () -> SystemOut.out.println("Hello World!");
    
  2. 有一个参数,无返回值

    Consumer<String> consumer = (x) -> System.out.println("Hello " + x);
    consumer.accept("World");
    

    只有一个参数的情况下,可以将参数的小括号去掉。

    Consumer<String> consumer = x -> System.out.println("Hello " + x);
    consumer.accept("World");
    
  3. 有多个参数,且有返回值。并且Lambda体中有多条语句,多条语句,必须加大括号

    Comparator<Integer> com = (x, y) -> {
      System.out.println("调用比较函数");
      return Integer.compare(o1, o2);
    };
    
  4. 有多个参数,且有返回值,并且Lambda体中只有一条语句,那么lambda体中的大括号可以省略,并且return也可以省略

    Comparator<Integer> com = (x, y) -> Integer.compare(o1, o2);
    
  5. Lambda表达式的参数列表的数据类型可以省略不写,因为JVM表一起通过上下文可以推断出数据类型,既 类型推断

    List<String> stringList = new ArrayList<>();
    

1.2. 函数式接口

函数式接口:接口中只有一个抽象方法的接口,成为函数式接口。可以使用注解@FunctionalInterface修饰,注解的作用是用来检查接口是否是一个函数式接口。

1.2.1. java8内置的四大函数式接口

java8在rt.jar中的 java.util.function 包里提供了很多函数式接口

  1. Consumer<T>: 消费型接口

    void accept(T t);

  2. Supplier<T>:供给型接口

    T get();

  3. Function<T,R>:函数型接口

    R apply(T t);

  4. Predicate<T>:断言型接口

    boolean test(T t);

1.3. 方法引用与构造器引用

1.3.1 方法引用

方法引用:若Lambda提中的内容已经有方法实现了,我们可以使用方法引用。

可以理解为方法引用是Lambda表达式的另一种表现形式。

主要有三种语法格式:

  • 对象::实例方法
  • 类 ::静态方法名
  • 类::实例方法名
  1. 对象::实例方法

    如果方法体中方法已经由其他对象的方法实现了,那么就可以使用obj::method来替代方法体。

    Consumer<String> con = x -> System.out.println(x);
    

    方法体中只有一个已经由System.out对象实现的方法,并且方法的参数和返回值和接口方法的参数和返回值都一致,所以可以使用方法引用。

    Consumer<String> con = System.out::print;
    
  2. 类::静态方法

    与上面类似,如果一个静态方法可以实现某个函数式接口的抽象类方法,那么可以使用这种方式来实现。

    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
    
    Comparator<Integer> com = Integer::compare;
    
  3. 类::实例方法名

    这种方式有点特殊,因为实例方法并不是类来调起的,而是由第一个参数调起的。

    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是数据渠道,用户操作数据源(集合、数组等)所生成的元素序列。集合讲得是数据,流讲得是计算

注意:

  1. Stream自己不会存储元素。
  2. Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream。
  3. Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行。

2.2. Stream操作

  1. 创建Stream:从一个数据源(如:集合、数组)中获取一个流
  2. 中间操作:一个中间操作链,对数据源的数据进行处理。
  3. 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果。
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()去除重复元素
  1. 准备

             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);
        }
    
  1. 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
    
  2. 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
    
  3. 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
    
  1. 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
    
  2. 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) 内部迭代
  1. allMatch

             @Test
        public void testAllMatch() {
            boolean res = employees.stream()
                    .filter(e -> e.getAge() > 35)
                    .allMatch(e -> e.getSalary() > 10000);
            System.out.println(res);
        }
    
  1. 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实用类提供了很多静态方法,可以方便地创建常用的收集器实例,具体的方法和实例如下表:

  1. 将流元素归约汇总成一个值

    方法 作用
    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
    
  2. 元素分组

    分组使用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]}
    
  3. 元素分区

    分区是分组的特殊情况,由一个 谓词(返回一个布尔值的函数)作为分类函数,称为分区函数。
    通俗来讲就是现在的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框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。

image
2.3.2. 并行流

并行流相对于自己实现的Fork/Join模型,在CPU利用率和运算速度上都显得更有优势。下面通过例子来实际对比一下。

计算1-100亿的运行速度:

  1. 常规方法:

    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
    
  1. 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
    
  2. 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 );
    }
}
  1. 当继承的父类和实现的接口中有相同签名的方法时,优先使用父类的方法。

  2. 当接口的父接口中也有同样的默认方法时,就近原则调用子接口的方法。

  3. 当实现的多个接口中有相同签名的方法时,必须在实现类中通过重写方法解决冲突问题,否者无法通过编译,在重写的方法中可以通过 接口名.super.方法名(); 的方式显示调用需要的方法。

5. 新时间日期API

5.1. 旧的时间日期api缺陷

  1. Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且都不是线程安全的。

  2. Date如果不格式化,打印出的日期可读性差。

    Wed Feb 05 00:31:12 CST 2020
    
  3. 可以使用 SimpleDateFormat 对时间进行格式化,但 SimpleDateFormat 是线程不安全的。

  4. 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. 基础类库
  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());
    
        }
    
  2. 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
    
    
  3. 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中加入了对市区的支持,带时区的时间分别为ZonedDateZonedTimeZonedDateTime。其中每个时区都对应着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 个错误
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • Int Double Long 设置特定的stream类型, 提高性能,增加特定的函数 无存储。stream不是一...
    patrick002阅读 1,267评论 0 0
  • 了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...
    龙历旗阅读 3,298评论 3 4
  • Java 8自Java 5(发行于2004)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发...
    huoyl0410阅读 613评论 1 2
  • 前言:Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geek...
    糖宝_阅读 1,320评论 1 1
  • 目录结构 介绍 Java语言的新特性2.1 Lambdas表达式与Functional接口2.2 接口的默认与静态...
    夜风月圆阅读 456评论 0 2