java8_java8特性 - CSDN
精华内容
参与话题
  • Java8新特性

    千次阅读 多人点赞 2019-10-21 11:12:20
    目录 1、Lambda表达式 2、函数式接口 3、接口的默认方法和静态方法 4、方法引用 4.1、引用方法 4.2、引用构造方法 4.3、引用数组 5、Optional 6、Stream 6.1、什么是Stream 6.2、Stream的特点 ...6....

    目录

    1、Lambda表达式

    2、函数式接口

    3、接口的默认方法和静态方法

    4、方法引用

    4.1、引用方法

    4.2、引用构造方法

    4.3、引用数组

    5、Optional

    6、Stream

    6.1、什么是Stream

    6.2、Stream的特点

    6.3、Stream的构成

    6.4、生成Stream的方式

    6.5、Stream的操作类型

    6.6、Stream的使用

    6.7、Stream总结

    7、Date/Time API

    7.1、LocalDate类

    7.2、LocalTime类

    7.3、LocalDateTime类

    7.4、ZoneDateTime类

    7.5、Clock类

    7.6、Duration类

    8、其他特性

    8.1、重复注解

    8.2、扩展注解

    8.3、更好的类型推测机制

    8.4、参数名字保留在字节码中

    8.5、并行数组

    8.6、CompletableFuture

    8.7、JVM的新特性


    1、Lambda表达式

    在JDK8之前,一个方法能接受的参数都是变量,例如: object.method(Object o)
    那么,如果需要传入一个动作呢?比如回调。
    那么你可能会想到匿名内部类。
    例如:
    匿名内部类是需要依赖接口的,所以需要先定义个接口

    public interface PersonCallBack {
    
        void callBack(Person person);
    
    }

    Person类

    public class Person {
    
        private int id;
    
        private String name;
    
        public Person (int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        /**
         * 创建一个Person后进行回调
         * @param id
         * @param name
         * @param personCallBack
         */
        public static void create(Integer id, String name, PersonCallBack personCallBack) {
            Person person = new Person(id, name);
            personCallBack.callBack(person);
        }
    
    }

    调用方法:

            //  调用方法,传入回调类,传统方式,使用匿名内部类
            Person.create(1, "zhangsan", new PersonCallBack() {
                @Override
                public void callBack(Person person) {
                    System.out.println("callback -- " +person.getName());
                }
            });

    上面的PersonCallback其实就是一种动作,但是我们真正关心的只有callback方法里的内容而已,我们用Lambda
    表示,可以将上面的代码就可以优化成:

            // 使用lambda表达式实现
            Person.create(2, "lisi", (person) -> {System.out.println("lambda callback -- " +person.getName());});
            // 进一步简化
            // 这归功于Java8的类型推导机制。因为现在接口里只有一个方法,那么现在这个Lambda表达式肯定是对应实现了这个方法,
            // 既然是唯一的对应关系,那么入参肯定是Person类,所以可以简写,
            // 并且方法体只有唯一的一条语句,所以也可以简写,以达到表达式简洁的效果。
            Person.create(2, "lisi", person -> System.out.println("lambda callback -- " +person.getName()));

    Lambda允许把函数作为一个方法的参数,一个lambda由用逗号分隔的参数列表、–>符号、函数体三部分表示。

    一个Lambda表达式实现了接口里的有且仅有的唯一一个抽象方法。那么对于这种接口就叫做函数式接口

    Lambda表达式其实完成了实现接口并且实现接口里的方法这一功能,也可以认为Lambda表达式代表一种动作,

    我们可以直接把这种特殊的动作进行传递。

    2、函数式接口

    函数式接口是新增的一种接口定义。
    @FunctionalInterface修饰的接口叫做函数式接口,或者,函数式接口就是一个只具有一个抽象方法的普通接口,@FunctionalInterface可以起到校验的作用。

    @FunctionalInterface
    public interface MyFunctionInterface {
    
        /**
         * 函数式接口的唯一抽象方法
         */
        void method();
    
    }

    在JDK7中其实就已经有一些函数式接口了,比如Runnable、Callable、FileFilter等等。
    在JDK8中也增加了很多函数式接口,比如java.util.function包。
    比如这四个常用的接口:

    一个Lambda表达式其实也可以理解为一个函数式接口的实现者,但是作为表达式,它的写法
    其实是多种多样的,比如

    • () -> {return 0;},没有传入参数,有返回值
    • (int i) -> {return 0;},传入一个参数,有返回值
    • (int i) -> {System.out.println(i)},传入一个int类型的参数,但是没有返回值
    • (int i, int j) -> {System.out.println(i)},传入两个int类型的参数,但是没有返回值
    • (int i, int j) -> {return i+j;},传入两个int类型的参数,返回一个int值
    • (int i, int j) -> {return i>j;},传入两个int类型的参数,返回一个boolean值

    那么这每种表达式的写法其实都应该是某个函数式接口的实现类,需要特定函数式接口进行对应,比如上面的四种情况就分别对应:Supplier<T> , Function<T,R> , Consumer<T> , BiConsumer<T, U> , BiFunction<T, U, R> , BiPredicate<T, U> 。
    Java8中提供给我们这么多函数式接口就是为了让我们写Lambda表达式更加方便,当然遇到特殊情况,你还是需要定义你自己的函数式接口然后才能写对应的Lambda表达式。
    总的来说,如果没有函数式接口,就不能写Lambda表达式。

    3、接口的默认方法和静态方法

    在接口中用default修饰的方法称为默认方法
    接口中的默认方法一定要有默认实现(方法体),接口实现者可以继承它,也可以覆盖它。

        /**
         * 接口的默认方法
         * @param s
         * @return
         */
        default String methodDefault(String s) {
            System.out.println(s);
            return "res--" + s;
        }

    在接口中用static修饰的方法称为静态方法

        /**
         * 接口的静态方法
         * @param a
         * @param b
         * @return
         */
        static String methodStatic(String a, String b) {
            return a + b;
        }

    4、方法引用

    4.1、引用方法

    • 实例对象::实例方法名
    • 类名::静态方法名
    • 类名::实例方法名
            // 引用方法1   实例对象::实例方法名
            // System.out代表的就是PrintStream类型的一个实例,println是这个实例的一个方法
    //        Consumer<String> consumer1 = s -> System.out.println(s);
            Consumer<String> consumer2 = System.out::println;
            consumer2.accept("呵呵");
    
            // 引用方法2   类名::静态方法名
            // Function中的唯一抽象方法apply方法参数列表与abs方法的参数列表相同,都是接收一个Long类型参数。
    //        Function<Long, Long> f = x -> Math.abs(x);
            Function<Long, Long> f = Math::abs;
            Long result = f.apply(-3L);
    
            // 引用方法3  类名::实例方法名
            // 若Lambda表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法
    //        BiPredicate<String, String> b = (x,y) -> x.equals(y);
            BiPredicate<String, String> b = String::equals;
            b.test("a", "b");

    4.2、引用构造方法

            // 引用构造方法
            // 在引用构造方法的时候,构造方法参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new
    //        Function<Integer, StringBuffer> fun = n -> new StringBuffer(n);
            Function<Integer, StringBuffer> fun = StringBuffer::new;
            StringBuffer buffer = fun.apply(10);

    4.3、引用数组

            // 引用数组
            // 引用数组和引用构造器很像,格式为 类型[]::new,其中类型可以为基本类型也可以是类
    //        Function<Integer, int[]> fun = n -> new int[n];
            Function<Integer, int[]> fun1 = int[]::new;
            int[] arr = fun1.apply(10);
            Function<Integer, Integer[]> fun2 = Integer[]::new;
            Integer[] arr2 = fun2.apply(10);

    5、Optional

    Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    创建Optional对象的几个方法:

    1. Optional.of(T value), 返回一个Optional对象,value不能为空,否则会出空指针异常
    2. Optional.ofNullable(T value), 返回一个Optional对象,value可以为空
    3. Optional.empty(),代表空

    其他API:

    1.  optional.isPresent(),是否存在值(不为空)
    2.  optional.ifPresent(Consumer<? super T> consumer), 如果存在值则执行consumer
    3.  optional.get(),获取value
    4.  optional.orElse(T other),如果没值则返回other
    5.  optional.orElseGet(Supplier<? extends T> other),如果没值则执行other并返回
    6.  optional.orElseThrow(Supplier<? extends X> exceptionSupplier),如果没值则执行exceptionSupplier,并抛出异常

    高级API:

    1.  optional.map(Function<? super T, ? extends U> mapper),映射,映射规则由function指定,返回映射值的Optional,所以可以继续使用Optional的API。
    2.  optional.flatMap(Function<? super T, Optional< U > > mapper),同map类似,区别在于map中获取的返回值自动被Optional包装,flatMap中返回值保持不变,但入参必须是Optional类型。
    3.  optional.filter(Predicate<? super T> predicate),过滤,按predicate指定的规则进行过滤,不符合规则则返回empty,也可以继续使用Optional的API。
        /**
         * 防止空指针,使用Optional
         * @param person
         * @return
         */
        public static String getPersonNameOptional(Person person) {
            // 使用Optional对person进行包装
            return Optional.ofNullable(person).map((per) -> per.getName()).orElse(null);
        }
    
    
        /**
         * 防止空指针,原有实现方法
         * @param person
         * @return
         */
        public static String getPersonName(Person person) {
            // 防止空指针,进行非空判断
            if (person == null) {
                return null;
            }
            return person.getName();
        }

    使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于
    其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法。

    6、Stream

    Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

    在传统的 J2EE 应用中,Java 代码经常不得不依赖于关系型数据库的操作如:取平均值、取最大最小值、取汇总值、或者进行分组等等类似的这些操作。
    但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而 Java 的集合 API 中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用 Iterator 来遍历集合,完成相关的聚合应用逻辑。这是一种远不够高效、笨拙的方法。在Java 7 中,如果要找一年级的所有学生,然后返回按学生分数值降序排序好的学生ID的集合,我们需要这样写:

    public class Student {
    
        /**
         * ID
         */
        private Integer id;
    
        /**
         * 年级
         */
        private Grade grade;
    
        /**
         * 分数
         */
        private Integer score;
    
        public Student(Integer id, Grade grade, Integer score) {
            this.id = id;
            this.grade = grade;
            this.score = score;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Grade getGrade() {
            return grade;
        }
    
        public void setGrade(Grade grade) {
            this.grade = grade;
        }
    
        public Integer getScore() {
            return score;
        }
    
        public void setScore(Integer score) {
            this.score = score;
        }
    }
    public enum Grade {
    
        ONE, TWO, THREE
    
    }
        /**
         * 传统方法
         * @param studentList
         * @return
         */
        public static List<Integer> oldMethod(List<Student> studentList) {
    
            // 取出一年级学生
            List<Student> gradeOneStudentList = new ArrayList<>();
            for (Student student : studentList) {
                if (Grade.ONE.equals(student.getGrade())) {
                    gradeOneStudentList.add(student);
                }
            }
            // 按成绩排序
            Collections.sort(gradeOneStudentList, new Comparator<Student>() {
                @Override
                public int compare(Student o1, Student o2) {
                    return o1.getScore().compareTo(o2.getScore());
                }
            });
    //        Collections.sort(gradeOneStudentList, Comparator.comparing(Student::getScore));
    
            List<Integer> studentIdList = new ArrayList<>();
            for (Student student : gradeOneStudentList) {
                studentIdList.add(student.getId());
            }
    
            return studentIdList;
        }

    而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。

        /**
         * 使用stream
         * @param studentList
         * @return
         */
        public static List<Integer> newMethod(List<Student> studentList) {
            return studentList.stream()
                    .filter((student -> student.getGrade().equals(Grade.ONE)))
                    .sorted(Comparator.comparingInt(Student::getScore))
                    .map(Student::getId)
                    .collect(Collectors.toList());
        }

    6.1、什么是Stream

    Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。

    6.2、Stream的特点

    1.  Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作; Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
    2.  Stream 就如同一个Iterator,单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
    3.  Stream 可以并行化操作,Iterator只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架来拆分任务和加速处理过程。

    6.3、Stream的构成

    当我们使用一个流的时候,通常包括三个基本步骤:
    获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一
    个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道

    6.4、生成Stream的方式

      1. 从Collection 和数组生成
            Collection.stream()
            Collection.parallelStream()
            Arrays.stream(T array)
            Stream.of(T t)
      2. 从 BufferedReader
            java.io.BufferedReader.lines()
      3. 静态工厂
            java.util.stream.IntStream.range()
            java.nio.file.Files.walk()
      4. 自己构建
            java.util.Spliterator
      5. 其它
            Random.ints()
            BitSet.stream()
            Pattern.splitAsStream(java.lang.CharSequence)
            JarFile.stream()

    6.5、Stream的操作类型

    • 中间操作(Intermediate Operation):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    • 终止操作(Terminal Operation):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。

    Intermediate Operation又可以分为两种类型:

    • 无状态操作(Stateless Operation):操作是无状态的,不需要知道集合中其他元素的状态,每个元素之间是相互独立的,比如map()、filter()等操作。
    • 有状态操作(Stateful Operation):有状态操作,操作是需要知道集合中其他元素的状态才能进行的,比如sort()、distinct()。

    Terminal Operation从逻辑上可以分为两种:

    • 短路操作(short-circuiting):短路操作是指不需要处理完所有元素即可结束整个过程
    • 非短路操作(non-short-circuiting):非短路操作是需要处理完所有元素之后才能结束整个过程

    6.6、Stream的使用

    • 1. 构造流的几种常见方法
            // 1、构造流的常用方法
            // 1> Individual values
            Stream stream = Stream.of("a", "b", "c");
    
            // 2> Arrays
            String [] strArray = new String[] {"a", "b", "c"};
            stream = Stream.of(strArray);
            stream = Arrays.stream(strArray);
    
            // 3> Collections
            List<String> list = Arrays.asList(strArray);
            stream = list.stream();
            stream.forEach(System.out::println);
    • 2. 数值流的构造

        对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。

            // 2、数值流的构造
            // 对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream
            IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
            IntStream.range(1, 3).forEach(System.out::println);
            IntStream.rangeClosed(1, 3).forEach(System.out::println);
    • 3. 流转换为其他数据结构
            // 3、流转换为其他数据结构
            Stream<String> stream3 = Stream.of(new String[]{"1", "2", "3"});
    //        List<String> list1 = stream3.collect(Collectors.toList());
    //        List<String> list2 = stream3.collect(Collectors.toCollection(ArrayList::new));
            // 一个 Stream 只可以使用一次
            String str = stream3.collect(Collectors.joining());
            System.out.println(str);
    • 4. 流的典型用法
          // 4、流的典型用法
            // 1> map/flatMap
            // map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素
            Stream<String> stream4 = Stream.of(new String[]{"a", "b", "c"});
            stream4.map(String::toUpperCase).forEach(System.out::println);
    
            // 还有一些场景,是一对多映射关系的,这时需要 flatMap
            Stream<List<Integer>> inputStream = Stream.of(
                    Arrays.asList(1),
                    Arrays.asList(2, 3),
                    Arrays.asList(4, 5, 6)
            );
    //        Stream<Integer> mapStream = inputStream.map(List::size);
    //        mapStream.forEach(System.out::println);
            Stream<Integer> flatMapStream = inputStream.flatMap(Collection::stream);
            flatMapStream.forEach(System.out::println);
    
            // 2> filter
            // filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream
            Integer[] nums = new Integer[]{1,2,3,4,5,6};
            Arrays.stream(nums).filter(n -> n<3).forEach(System.out::println);
    
            // 3> forEach
            // forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个 Stream 进行两次terminal 运算
            Stream stream13 = Arrays.stream(nums);
            stream13.forEach(System.out::print);
    //        stream13.forEach(System.out::print); // 上面forEach已经消费掉了,不能再调用
            System.out.println();
    
            // 具有相似功能的 intermediate 操作 peek 可以达到上述目的
            Stream stream14 = Arrays.stream(nums);
            stream14
                    .peek(System.out::print)
                    .peek(System.out::print)
                    .collect(Collectors.toList());
            System.out.println();
    
            // 4> reduce 主要作用是把 Stream 元素组合起来,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce
            // Stream 的 sum 就相当于:
            Integer sum = Arrays.stream(nums).reduce(0, (integer, integer2) -> integer + integer2);
            System.out.println(sum);
            // 有初始值
            Integer sum1 = Arrays.stream(nums).reduce(0, Integer::sum);
            // 无初始值
            Integer sum2 = Arrays.stream(nums).reduce(Integer::sum).get();
    
            // 5> limit/skip
            // limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。
            Arrays.stream(nums).limit(3).forEach(System.out::print);
            System.out.println();
            Arrays.stream(nums).skip(2).forEach(System.out::print);
            System.out.println();
    
            // 6> sorted
    //        对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、
    //        limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
            Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).limit(3).forEach(System.out::print);
            System.out.println();
            Arrays.stream(nums).sorted((i1, i2) -> i2.compareTo(i1)).skip(2).forEach(System.out::print);
            System.out.println();
    
            // 7> min/max/distinct
            System.out.println(Arrays.stream(nums).min(Comparator.naturalOrder()).get());
            System.out.println(Arrays.stream(nums).max(Comparator.naturalOrder()).get());
            Arrays.stream(nums).distinct().forEach(System.out::print);
            System.out.println();
    
            // 8> Match
    //        Stream 有三个 match 方法,从语义上说:
    //        allMatch:Stream 中全部元素符合传入的 predicate,返回 true
    //        anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
    //        noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
    //        它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。
            Integer[] nums1 = new Integer[]{1, 2, 2, 3, 4, 5, 5, 6};
            System.out.println(Arrays.stream(nums1).allMatch(integer -> integer < 7));
            System.out.println(Arrays.stream(nums1).anyMatch(integer -> integer < 2));
            System.out.println(Arrays.stream(nums1).noneMatch(integer -> integer < 0));
    • 5. 用Collectors来进行reduction操作
            // 5、用Collectors来进行reduction操作
    //        java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
            // 1> groupingBy/partitioningBy
            final Collection<Student> students = Arrays.asList(
                    new Student(1, Grade.ONE, 60),
                    new Student(2, Grade.TWO, 80),
                    new Student(3, Grade.ONE, 100)
            );
            // 按年级进行分组groupingBy
            students.stream().collect(Collectors.groupingBy(Student::getGrade)).forEach(((grade,students1) -> {
                System.out.println(grade);
                students1.forEach(student ->
                        System.out.println(student.getId()+","+student.getGrade()+","+student.getScore()));
            }));
    
            // 按分数段分组partitioningBy
            students.stream().collect(Collectors.partitioningBy(student -> student.getScore()<=60)).forEach(((match, students1) -> {
                System.out.println(match);
                students1.forEach(student ->
                        System.out.println(student.getId()+","+student.getGrade()+","+student.getScore()));
            }));
    • 6. parallelStream
            // 6、parallelStream
    //        parallelStream其实就是一个并行执行的流.它通过默认的ForkJoinPool,可以提高你的多线程任务的速度
            Arrays.stream(nums).parallel().forEach(System.out::print);
            System.out.println();
            System.out.println(Arrays.stream(nums).parallel().reduce(Integer::sum).get());
    
            Arrays.stream(nums).forEach(System.out::print);
            System.out.println();
            System.out.println(Arrays.stream(nums).reduce(Integer::sum).get());

    parallelStream底层是使用的ForkJoin。而ForkJoin里面的线程是通过ForkJoinPool来运行的,Java 8为ForkJoinPool添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。它是ForkJoinPool类型上的一个静态元素。它拥有的默认线程数量等于运行计算机上的处理器数量,所以这里就出现了这个java进程里所有使用parallelStream的地方实际上是公用的同一个ForkJoinPool。parallelStream提供了更简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。

    6.7、Stream总结

    1. 不是数据结构,它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据
    2. 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素
    3. 所有 Stream 的操作必须以 lambda 表达式为参数
    4. 惰性化,很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始,Intermediate操作永远是惰性化的
    5. 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的

    7、Date/Time API

    Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。对日期与时间的操作一直是Java程序员最痛苦的地方之一。标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种情况(可以这么说,它们一定程度上更加复杂)。
    这种情况直接导致了Joda-Time——一个可替换标准日期/时间处理且功能非常强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响,并且吸取了其精髓。

    7.1、LocalDate类

        /**
         * LocaleDate只持有ISO-8601格式且无时区信息的日期部分
         */
        public static void testLocaleDate() {
            LocalDate date = LocalDate.now(); // 当前日期
    
            date = date.plusDays(1); // 增加一天
            date = date.plusMonths(1); // 减少一个月
            date = date.minusDays(1); // 减少一天
            date = date.minusMonths(1); // 减少一个月
            System.out.println(date);
        }

    7.2、LocalTime类

        /**
         * LocaleTime只持有ISO-8601格式且无时区信息的时间部分
         */
        public static void testLocaleTime() {
            LocalTime time = LocalTime.now(); // 当前时间
            time = time.plusMinutes(1); // 增加一分钟
            time = time.plusSeconds(1); // 增加一秒
            time = time.minusMinutes(1); // 减少一分钟
            time = time.minusSeconds(1); // 减少1秒
            System.out.println(time);
        }

    7.3、LocalDateTime类

        /**
         * LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,它持有的是ISO-8601格式无时区信息的日期与时间
         */
        public static void testLocalDateTime() {
            LocalDateTime localDateTime = LocalDateTime.now();
            System.out.println(localDateTime); // UTC格式
            System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); // 自定义格式
    
            // 原有方法
    //        Date nowDate = new Date();
    //        System.out.println(nowDate);
    //        System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").format(nowDate));
        }

    7.4、ZoneDateTime类

        /**
         * ZonedDateTime持有ISO-8601格式,具有时区信息的日期与时间。
         */
        public static void testZonedDateTime() {
            ZonedDateTime zonedDateTime =  ZonedDateTime.now();
            System.out.println(zonedDateTime);
    
            ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
            System.out.println(zonedDatetimeFromZone);
    
            ZoneId zoneId = ZoneId.systemDefault();
            System.out.println(zoneId);
        }

    7.5、Clock类

        /**
         * 它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。
         * Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()
         */
        public static void testClock() {
            Clock utc = Clock.systemUTC(); // 世界标准时间
            System.out.println(LocalDateTime.now(utc));
    
            Clock shanghai = Clock.system(ZoneId.of("Asia/Shanghai")); // 上海时间
            System.out.println(LocalDateTime.now(shanghai));
        }

    7.6、Duration类

        /**
         * Duration使计算两个日期间的不同变的十分简单。
         */
        public static void testDuration() {
            final LocalDateTime from = LocalDateTime.parse("2019-07-15 18:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            final LocalDateTime to = LocalDateTime.parse("2019-07-16 19:50:50", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            final Duration duration = Duration.between(from, to);
            System.out.println("Duration in days: " + duration.toDays()); // 1
            System.out.println("Duration in hours: " + duration.toHours()); // 25
        }

    8、其他特性

    8.1、重复注解

    假设,现在有一个服务我们需要定时运行,就像Linux中的cron一样,假设我们需要它在每周三的12点运行一次,那我们可能会定义一个注解,有两个代表时间的属性。

    @Retention( RetentionPolicy.RUNTIME )
    public @interface Schedule {
    
        int dayOfWeek() default 1; // 周几
    
        int hour() default 0; // 几点
    
    }

    所以我们可以给对应的服务方法上使用该注解,代表运行的时间:

    public class ScheduleService {
    
        //  每周三的12点运行
        @Schedule(dayOfWeek = 3, hour = 12)
        public void starts() {
    
        }
    
    }

    那么如果我们需要这个服务在每周四的13点也需要运行一下,如果是JDK8之前,那么...尴尬了!你不能像下面的代码,会编译错误

    public class ScheduleService {
    
        //  每周三的12点运行
        @Schedule(dayOfWeek = 3, hour = 12)
        @Schedule(dayOfWeek = 4, hour = 13)
        public void starts() {
    
        }
    
    }

    那么如果是JDK8,你可以改一下注解的代码,在自定义注解上加上@Repeatable元注解,并且指定重复注解的存储注解(其实就是需要需要数组来存储重复注解),这样就可以解决上面的编译报错问题。

    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable(value = Schedule.schedules.class)
    public @interface Schedule {
    
        int dayOfWeek() default 1; // 周几
    
        int hour() default 0; // 几点
    
        @Retention( RetentionPolicy.RUNTIME )
        @interface schedules {
            Schedule[] value();
        }
    
    }

    同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型。

        public static void main(String[] args) throws NoSuchMethodException {
            Method method = ScheduleService.class.getMethod("starts");
            for (Annotation annotation : method.getAnnotations()) {
                System.out.println(annotation);
            }
    
            for (Schedule schedule : method.getAnnotationsByType(Schedule.class)) {
                System.out.println(schedule.dayOfWeek() + "--" + schedule.hour());
            }
        }

    输出结果:

    @com.zjm.other.Schedule$schedules(value=[@com.zjm.other.Schedule(hour=12, dayOfWeek=3), @com.zjm.other.Schedule(hour=13, dayOfWeek=4)])
    3--12
    4--13 

    8.2、扩展注解

    注解可以加在:

    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
    
        /** Field declaration (includes enum constants) */
        FIELD,
    
        /** Method declaration */
        METHOD,
    
        /** Formal parameter declaration */
        PARAMETER,
    
        /** Constructor declaration */
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
        ANNOTATION_TYPE,
    
        /** Package declaration */
        PACKAGE,
    
        /**
         * Type parameter declaration
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8
         */
        TYPE_USE
    }

    JDK8中新增了两种:
    1. TYPE_PARAMETER,表示该注解能写在类型变量的声明语句中。
    2. TYPE_USE,表示该注解能写在使用类型的任何语句中

    8.3、更好的类型推测机制

    public class Value<T> {
    
        public static<T> T defaultValue() {
            return null;
        }
        public T getOrDefault(T value, T defaultValue) {
            return value != null ? value : defaultValue;
        }
        public static void main(String[] args) {
            Value<String> value = new Value<>();
            System.out.println(value.getOrDefault("22", Value.defaultValue()));
        }
        
    }

    上面的代码重点关注value.getOrDefault("22", Value.defaultValue()), 在JDK8中不会报错,那么在JDK7中呢?
    答案是会报错: Wrong 2nd argument type. Found: 'java.lang.Object', required:'java.lang.String' 。所以Value.defaultValue()的参数类型在JDK8中可以被推测出,所以就不必明确给出。

    8.4、参数名字保留在字节码中

    先来想一个问题:JDK8之前,怎么获取一个方法的参数名列表?
    在JDK7中一个Method对象有下列方法:

    • Method.getParameterAnnotations() 获取方法参数上的注解
    • Method.getParameterTypes() 获取方法的参数类型列表

    但是没有能够获取到方法的参数名字列表!
    在JDK8中增加了两个方法

    • Method.getParameters() 获取参数名字列表
    • Method.getParameterCount() 获取参数名字个数
    public class ParameterNames {
    
        public void test(String p1, String p2) {
    
        }
    
        public static void main(String[] args) throws Exception {
            Method method = ParameterNames.class.getMethod("test", String.class, String.class);
            for (Parameter parameter : method.getParameters()) {
                System.out.println(parameter.getName());
            }
            System.out.println(method.getParameterCount());
        }
        
    }

    输出结果:

    arg0
    arg1
    2

    从结果可以看出输出的参数个数正确,但是名字不正确!需要在编译时增加–parameters参数后再运行。
    在Maven中增加:

            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
              <configuration>
                <compilerArgument>-parameters</compilerArgument>
                <source>1.8</source>
                <target>1.8</target>
              </configuration>
            </plugin>

    输出结果:

    p1
    p2
    2

    8.5、并行数组

    Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。下面的例子展示了新方法(parallelXxx)的使用。
    下面的代码演示了先并行随机生成20000个0-1000000的数字,然后打印前10个数字,然后使用并行排序,再次打印前10个数字。

            long[] arrayOfLong = new long [ 20000 ];
            Arrays.parallelSetAll(arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 1000000 ));
            Arrays.stream(arrayOfLong).limit( 10 ).forEach(i -> System.out.print( i + " " ));
            System.out.println();
            Arrays.parallelSort(arrayOfLong);
            Arrays.stream(arrayOfLong).limit( 10 ).forEach(i -> System.out.print( i + " " ));
            System.out.println();

    8.6、CompletableFuture

    当我们Javer说异步调用时,我们自然会想到Future,比如:

    public class FutureTest {
        public static void main(String[] args) throws Exception {
            ExecutorService executor = Executors.newCachedThreadPool();
            Future<Integer> result = executor.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum=0;
                    System.out.println(Thread.currentThread().getName()+"正在计算...");
                    for (int i=0; i<100; i++) {
                        sum = sum + i;
                    }
                    Thread.sleep(TimeUnit.SECONDS.toSeconds(3000));
                    System.out.println(Thread.currentThread().getName()+"计算完了!");
                    return sum;
                }
            });
            System.out.println(Thread.currentThread().getName()+"做其他事情...");
            System.out.println(Thread.currentThread().getName()+"计算结果:" + result.get());
            System.out.println(Thread.currentThread().getName()+"事情都做完了!");
            executor.shutdown();
        }
    }

    输出结果:

    main做其他事情...
    pool-1-thread-1正在计算...
    pool-1-thread-1计算完了!
    main计算结果:4950
    main事情都做完了!

    那么现在如果想实现异步计算完成之后,立马能拿到这个结果继续异步做其他事情呢?这个问题就是一个线程依赖另外一个线程,这个时候Future就不方便,我们来看一下CompletableFuture的实现:

    public class CompletableFutureTest {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(10);
            CompletableFuture result = CompletableFuture.supplyAsync(() -> {
                int sum=0;
                System.out.println(Thread.currentThread().getName()+"正在计算...");
                for (int i=0; i<100; i++) {
                    sum = sum + i;
                }
                try {
                    Thread.sleep(TimeUnit.SECONDS.toSeconds(3000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"计算完了!");
                return sum;
            }, executor).thenApplyAsync(sum -> {
                System.out.println(Thread.currentThread().getName()+"打印:"+sum);
                return sum;
            }, executor);
            System.out.println(Thread.currentThread().getName()+"做其他事情...");
            try {
                System.out.println(Thread.currentThread().getName()+"计算结果:" + result.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"事情都做完了!");
            executor.shutdown();
        }
    }

    输出结果:

    main做其他事情...
    pool-1-thread-1正在计算...
    pool-1-thread-1计算完了!
    pool-1-thread-2打印:4950
    main计算结果:4950
    main事情都做完了!

    只需要简单的使用thenApplyAsync就可以实现了。

    8.7、JVM的新特性

    PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。JVM选项-XX:PermSize与-XX:MaxPermSize分
    别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。

    展开全文
  • java8安装

    万次阅读 2019-05-19 10:31:50
    首先获取java8的安装包 可以通过百度搜索jdk1.8,第一个点击进入即为下载页面 点击进入还会有jdk8版本的不同更新时间的小版本,推荐使用最后更新的那个,大致如下图 点击接受,然后可以选择下载,可以根据自己系统...

    jdk8应该是现在使用最多的一个吧,可是还有些小伙伴不知道怎么安装jdk8(jdk8等同于jdk1.8)
    下面就跟大家一起分享下安装的步骤吧

    首先获取java8的安装包

    可以通过百度搜索jdk1.8,第一个点击进入即为下载页面
    搜索java8安装包

    点击进入还会有jdk8版本的不同更新时间的小版本,推荐使用最后更新的那个,大致如下图
    jdk
    点击接受,然后可以选择下载,可以根据自己系统进行下载,这里以windows64为例,点击下载(有时会让登录)
    下载完成之后双击exe文件进行安装,下一步,选择安装路径
    java安装
    这里我选择安装到D盘,software目录下,然后下一步,接着会让选择jre的安装位置(自定义路径或者接受默认位置都可以),可能有同学会有疑问怎么选择两次安装路径,这里解释一下,第一次选择的是jdk(java开发工具包),第二次选择的是jre(java的运行环境),安装完成,接下来要配置环境变量

    配置环境变量

    1. 右击我的电脑,属性在这里插入图片描述
    2. 然后点击高级系统变量在这里插入图片描述
    3. 接着点击环境变量在这里插入图片描述
    4. 新建系统变量JAVA_HOME,然后值为刚才安装的jdk目录系统变量
    5. 然后把JAVA_HOME添加到path 即,双击path,添加%JAVA_HOME%\bin在这里插入图片描述
      安装完成全部点击确定之后即安装完成,可以打开cmd窗口(windows键+R可以快捷打开,输入cmd,回车),输入java -version ,出现下图的java版本,即安装成功
      在这里插入图片描述
    展开全文
  • JAVA8十大新特性详解

    万次阅读 2018-05-09 09:50:18
    转自https://blog.csdn.net/Mr_LI3306/article/details/80239339一、接口的默认方法Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:复制代码代码...

    转自https://blog.csdn.net/Mr_LI3306/article/details/80239339

    一、接口的默认方法

    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

    复制代码代码如下:                                                                                                             

    interface Formula {
        double calculate(int a);

        default double sqrt(int a) {
            return Math.sqrt(a);
        }
    }


    Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
    复制代码代码如下:

    Formula formula = new Formula() {
        @Override
        public double calculate(int a) {
            return sqrt(a * 100);
        }
    };

    formula.calculate(100);     // 100.0
    formula.sqrt(16);           // 4.0


    文中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到实现单方法接口的更简单的做法。

    译者注: 在Java中只有单继承,如果要让一个类赋予新的特性,通常是使用接口来实现,在C++中支持多继承,允许一个子类同时具有多个父类的接口与功能,在其他语言中,让一个类同时具有其他的可复用代码的方法叫做mixin。新的Java 8 的这个特新在编译器实现的角度上来说更加接近Scala的trait。 在C#中也有名为扩展方法的概念,允许给已存在的类型扩展方法,和Java 8的这个在语义上有差别。

    二、Lambda 表达式

    首先看看在老版本的Java中是如何排列字符串的:

    复制代码代码如下:

    List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

    Collections.sort(names, new Comparator<String>() {
        @Override
        public int compare(String a, String b) {
            return b.compareTo(a);
        }
    });


    只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。

    在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

    复制代码代码如下:

    Collections.sort(names, (String a, String b) -> {
        return b.compareTo(a);
    });

    看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:
    复制代码代码如下:

    Collections.sort(names, (String a, String b) -> b.compareTo(a));

    对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点:
    复制代码代码如下:

    Collections.sort(names, (a, b) -> b.compareTo(a));

    Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还能作出什么更方便的东西来:

    三、函数式接口

    Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

    我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

    示例如下:

    复制代码代码如下:

    @FunctionalInterface
    interface Converter<F, T> {
        T convert(F from);
    }
    Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
    Integer converted = converter.convert("123");
    System.out.println(converted);    // 123

    需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。

    译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。

    四、方法与构造函数引用

    前一节中的代码还可以通过静态方法引用来表示:

    复制代码代码如下:

    Converter<String, Integer> converter = Integer::valueOf;
    Integer converted = converter.convert("123");
    System.out.println(converted);   // 123

    Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:
    复制代码代码如下:

     converter = something::startsWith;
    String converted = converter.convert("Java");
    System.out.println(converted);    // "J"

    接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
    复制代码代码如下:

    class Person {
        String firstName;
        String lastName;

        Person() {}

        Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }


    接下来我们指定一个用来创建Person对象的对象工厂接口:
    复制代码代码如下:

    interface PersonFactory<P extends Person> {
        P create(String firstName, String lastName);
    }

    这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:
    复制代码代码如下:

    PersonFactory<Person> personFactory = Person::new;
    Person person = personFactory.create("Peter", "Parker");

    我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

    五、Lambda 作用域

    在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

    六、访问局部变量

    我们可以直接在lambda表达式中访问外层的局部变量:

    复制代码代码如下:

    final int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3


    但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:
    复制代码代码如下:

    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3


    不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:
    复制代码代码如下:

    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);
    num = 3;

    在lambda表达式中试图修改num同样是不允许的。

    七、访问对象字段与静态变量

    和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

    复制代码代码如下:
    class Lambda4 {
        static int outerStaticNum;
        int outerNum;

        void testScopes() {
            Converter<Integer, String> stringConverter1 = (from) -> {
                outerNum = 23;
                return String.valueOf(from);
            };

            Converter<Integer, String> stringConverter2 = (from) -> {
                outerStaticNum = 72;
                return String.valueOf(from);
            };
        }
    }



    八、访问接口的默认方法

    还记得第一节中的formula例子么,接口Formula定义了一个默认方法sqrt可以直接被formula的实例包括匿名对象访问到,但是在lambda表达式中这个是不行的。
    Lambda表达式中是无法访问到默认方法的,以下代码将无法编译:
    复制代码代码如下:

    Formula formula = (a) -> sqrt( a * 100);
    Built-in Functional Interfaces

    JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
    Java 8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

    Predicate接口

    Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):

    复制代码代码如下:

    Predicate<String> predicate = (s) -> s.length() > 0;

    predicate.test("foo");              // true
    predicate.negate().test("foo");     // false

    Predicate<Boolean> nonNull = Objects::nonNull;
    Predicate<Boolean> isNull = Objects::isNull;

    Predicate<String> isEmpty = String::isEmpty;
    Predicate<String> isNotEmpty = isEmpty.negate();



    Function 接口

    Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):

    复制代码代码如下:

    Function<String, Integer> toInteger = Integer::valueOf;
    Function<String, String> backToString = toInteger.andThen(String::valueOf);

    backToString.apply("123");     // "123"



    Supplier 接口

    Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
    复制代码代码如下:

    Supplier<Person> personSupplier = Person::new;
    personSupplier.get();   // new Person


    Consumer 接口

    Consumer 接口表示执行在单个参数上的操作。
    复制代码代码如下:

    Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
    greeter.accept(new Person("Luke", "Skywalker"));


    Comparator 接口

    Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
    复制代码代码如下:

    Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

    Person p1 = new Person("John", "Doe");
    Person p2 = new Person("Alice", "Wonderland");

    comparator.compare(p1, p2);             // > 0
    comparator.reversed().compare(p1, p2);  // < 0



    Optional 接口

    Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:

    Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。

    复制代码代码如下:

    Optional<String> optional = Optional.of("bam");

    optional.isPresent();           // true
    optional.get();                 // "bam"
    optional.orElse("fallback");    // "bam"

    optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"



    Stream 接口

    java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

    首先看看Stream是怎么用,首先创建实例代码的用到的数据List:

    复制代码代码如下:

    List<String> stringCollection = new ArrayList<>();
    stringCollection.add("ddd2");
    stringCollection.add("aaa2");
    stringCollection.add("bbb1");
    stringCollection.add("aaa1");
    stringCollection.add("bbb3");
    stringCollection.add("ccc");
    stringCollection.add("bbb2");
    stringCollection.add("ddd1");

    Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:

    Filter 过滤

    过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

    复制代码代码如下:

    stringCollection
        .stream()
        .filter((s) -> s.startsWith("a"))
        .forEach(System.out::println);

    // "aaa2", "aaa1"



    Sort 排序

    排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。

    复制代码代码如下:

    stringCollection
        .stream()
        .sorted()
        .filter((s) -> s.startsWith("a"))
        .forEach(System.out::println);

    // "aaa1", "aaa2"


    需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
    复制代码代码如下:

    System.out.println(stringCollection);
    // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1


    Map 映射

    中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
    复制代码代码如下:

    stringCollection
        .stream()
        .map(String::toUpperCase)
        .sorted((a, b) -> b.compareTo(a))
        .forEach(System.out::println);

    // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"



    Match 匹配

    Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

    复制代码代码如下:

    boolean anyStartsWithA = 
        stringCollection
            .stream()
            .anyMatch((s) -> s.startsWith("a"));

    System.out.println(anyStartsWithA);      // true

    boolean allStartsWithA = 
        stringCollection
            .stream()
            .allMatch((s) -> s.startsWith("a"));

    System.out.println(allStartsWithA);      // false

    boolean noneStartsWithZ = 
        stringCollection
            .stream()
            .noneMatch((s) -> s.startsWith("z"));

    System.out.println(noneStartsWithZ);      // true

    Count 计数

    计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

    复制代码代码如下:

    long startsWithB = 
        stringCollection
            .stream()
            .filter((s) -> s.startsWith("b"))
            .count();

    System.out.println(startsWithB);    // 3



    Reduce 规约

    这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:

    复制代码代码如下:

    Optional<String> reduced =
        stringCollection
            .stream()
            .sorted()
            .reduce((s1, s2) -> s1 + "#" + s2);

    reduced.ifPresent(System.out::println);
    // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"



    并行Streams

    前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

    下面的例子展示了是如何通过并行Stream来提升性能:

    首先我们创建一个没有重复元素的大表:

    复制代码代码如下:

    int max = 1000000;
    List<String> values = new ArrayList<>(max);
    for (int i = 0; i < max; i++) {
        UUID uuid = UUID.randomUUID();
        values.add(uuid.toString());
    }

    然后我们计算一下排序这个Stream要耗时多久,
    串行排序:
    复制代码代码如下:

    long t0 = System.nanoTime();

    long count = values.stream().sorted().count();
    System.out.println(count);

    long t1 = System.nanoTime();

    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
    System.out.println(String.format("sequential sort took: %d ms", millis));

    // 串行耗时: 899 ms
    并行排序:

    复制代码代码如下:

    long t0 = System.nanoTime();

    long count = values.parallelStream().sorted().count();
    System.out.println(count);

    long t1 = System.nanoTime();

    long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
    System.out.println(String.format("parallel sort took: %d ms", millis));

    // 并行排序耗时: 472 ms
    上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。

    Map

    前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。

    复制代码代码如下:

    Map<Integer, String> map = new HashMap<>();

    for (int i = 0; i < 10; i++) {
        map.putIfAbsent(i, "val" + i);
    }

    map.forEach((id, val) -> System.out.println(val));
    以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。

    下面的例子展示了map上的其他有用的函数:

    复制代码代码如下:

    map.computeIfPresent(3, (num, val) -> val + num);
    map.get(3);             // val33

    map.computeIfPresent(9, (num, val) -> null);
    map.containsKey(9);     // false

    map.computeIfAbsent(23, num -> "val" + num);
    map.containsKey(23);    // true

    map.computeIfAbsent(3, num -> "bam");
    map.get(3);             // val33


    接下来展示如何在Map里删除一个键值全都匹配的项:
    复制代码代码如下:

    map.remove(3, "val3");
    map.get(3);             // val33

    map.remove(3, "val33");
    map.get(3);             // null


    另外一个有用的方法:
    复制代码代码如下:

    map.getOrDefault(42, "not found");  // not found

    对Map的元素做合并也变得很容易了:
    复制代码代码如下:

    map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
    map.get(9);             // val9

    map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
    map.get(9);             // val9concat


    Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

    九、Date API

    Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:

    Clock 时钟

    Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

    复制代码代码如下:

    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();

    Instant instant = clock.instant();
    Date legacyDate = Date.from(instant);   // legacy java.util.Date



    Timezones 时区

    在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。

    复制代码代码如下:

    System.out.println(ZoneId.getAvailableZoneIds());
    // prints all available timezone ids

    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    System.out.println(zone1.getRules());
    System.out.println(zone2.getRules());

    // ZoneRules[currentStandardOffset=+01:00]
    // ZoneRules[currentStandardOffset=-03:00]



    LocalTime 本地时间

    LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:

    复制代码代码如下:

    LocalTime now1 = LocalTime.now(zone1);
    LocalTime now2 = LocalTime.now(zone2);

    System.out.println(now1.isBefore(now2));  // false

    long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
    long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

    System.out.println(hoursBetween);       // -3
    System.out.println(minutesBetween);     // -239


    LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
    复制代码代码如下:

    LocalTime late = LocalTime.of(23, 59, 59);
    System.out.println(late);       // 23:59:59

    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedTime(FormatStyle.SHORT)
            .withLocale(Locale.GERMAN);

    LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
    System.out.println(leetTime);   // 13:37


    LocalDate 本地日期

    LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

    复制代码代码如下:

    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
    LocalDate yesterday = tomorrow.minusDays(2);

    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();


    System.out.println(dayOfWeek);    // FRIDAY
    从字符串解析一个LocalDate类型和解析LocalTime一样简单:
    复制代码代码如下:

    DateTimeFormatter germanFormatter =
        DateTimeFormatter
            .ofLocalizedDate(FormatStyle.MEDIUM)
            .withLocale(Locale.GERMAN);

    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
    System.out.println(xmas);   // 2014-12-24



    LocalDateTime 本地日期时间

    LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

    复制代码代码如下:

    LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

    DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
    System.out.println(dayOfWeek);      // WEDNESDAY

    Month month = sylvester.getMonth();
    System.out.println(month);          // DECEMBER

    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
    System.out.println(minuteOfDay);    // 1439


    只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
    复制代码代码如下:

    Instant instant = sylvester
            .atZone(ZoneId.systemDefault())
            .toInstant();

    Date legacyDate = Date.from(instant);
    System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014


    格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
    复制代码代码如下:

    DateTimeFormatter formatter =
        DateTimeFormatter
            .ofPattern("MMM dd, yyyy - HH:mm");

    LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
    String string = formatter.format(parsed);
    System.out.println(string);     // Nov 03, 2014 - 07:13


    和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
    关于时间日期格式的详细信息:http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html

    十、Annotation 注解

    在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。
    首先定义一个包装类Hints注解用来放置一组具体的Hint注解:

    复制代码代码如下:

    @interface Hints {
        Hint[] value();
    }

    @Repeatable(Hints.class)
    @interface Hint {
        String value();
    }


    Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。

    例 1: 使用包装类当容器来存多个注解(老方法)

    复制代码代码如下:

    @Hints({@Hint("hint1"), @Hint("hint2")})
    class Person {}

    例 2:使用多重注解(新方法)
    复制代码代码如下:

    @Hint("hint1")
    @Hint("hint2")
    class Person {}

    第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息:
    复制代码代码如下:

    Hint hint = Person.class.getAnnotation(Hint.class);
    System.out.println(hint);                   // null

    Hints hints1 = Person.class.getAnnotation(Hints.class);
    System.out.println(hints1.value().length);  // 2

    Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
    System.out.println(hints2.length);          // 2


    即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class) 来获取 @Hints注解,更加方便的方法是使用 getAnnotationsByType 可以直接获取到所有的@Hint注解。
    另外Java 8的注解还增加到两种新的target上了:
    复制代码代码如下:

    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
    @interface MyAnnotation {}

    关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如Arrays.parallelSort, StampedLock和CompletableFuture等等。
    展开全文
  • Java8最全介绍(持续更新)

    千次阅读 2019-05-10 15:20:02
    Lambda表达式 Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它 ...// Java8以前 Comparator<Apple> byWeight = new Comparator<Apple>() { public int compare(Apple ...

    Lambda表达式

    Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它
    

    有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

    举个例子:

    // Java8以前
    Comparator<Apple> byWeight = new Comparator<Apple>() {
        public int compare(Apple a1, Apple a2) {
            return a1.getWeight().compareTo(a2.getWeight());
        }
    }
    
    // Java8,使用了Lambda表达式
    Comparator<Apple> byWeight = (Apple a1, Apple a2) -> 		       a1.getWeight().compareTo(a2.getWeight());
    

    如何使用Lambda表达式?

    1. 参数列表:这里采用了Comparator中compare方法的参数,两个 Apple 。
    2. 箭头:箭头 -> 把参数列表与Lambda主体分隔开。
    3. Lambda主体:比较两个 Apple 的重量。表达式就是Lambda的返回值了。

    注意: 可以不需要写参数类型,单条语句不需要写return,多条语句需要使用{ }括号括起来。

    在哪里使用Lambda表达式?

    在函数式接口上使用Lambda表达式。
    

    函数式接口

    函数式接口就是只定义一个抽象方法的接口。
    
    // 下面哪些是函数式接口?
    public interface Adder {
        int add(int a, int b);
    }
    public interface SmartAdder extends Adder{
        int add(double a, double b);
    }
    public interface Nothing{
        
    }
    
    答案: 只有Adder是函数式接口。SmartAdder不是函数式接口,因为它继承了一个Adder又自己定义了一个。Nothing则没有声明抽象方法。
    

    函数式接口能干什么?

    Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。 具体来说就是函数式接口一个具体实现的实例。
    
    其实,使用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后直接内联将它实例化。比如下面的Runnable的run方法。
    
    // 使用匿名内部类的方式
    Runnable r = new Runnabler(){
        @Override
        public void run(){
            System.out.println("Hello World!");
        }
    }
    
    // 使用Lambda
    Runnable r = () -> System.out.println("Hello World!");
    

    使用函数式接口

    1. Predicate
      java.util.function.Predicate接口定义了一个名叫test的抽象方法,他接受泛型T对象,并返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。从下面Predicate的源码中也可以看到其他的默认实现方法,这些将会在后面进行解答。

         // 底层源码
         @FunctionalInterface
         public interface Predicate<T>{
         	   boolean test(T t);
             
             default Predicate<T> and(Predicate<? super T> other) {
                 Objects.requireNonNull(other);
                 return (t) -> test(t) && other.test(t);
             }
             
             default Predicate<T> negate() {
                 return (t) -> !test(t);
             }
             
             default Predicate<T> or(Predicate<? super T> other) {
                 Objects.requireNonNull(other);
                 return (t) -> test(t) || other.test(t);
             }
             
             static <T> Predicate<T> isEqual(Object targetRef) {
                 return (null == targetRef)
                         ? Objects::isNull
                         : object -> targetRef.equals(object);
             }
         }
      

      先看一个栗子:

       public static <T> List<T> filter(List<T> list, Predicate<T> p) {
          	List<T> results = new ArrayList<>();
          	for(T s: list){
          		if(p.test(s)){
          			results.add(s);
          		}
          	}
          	return results;
          }
      
      // 利用Lambda实现一个判断字符串是否为空的方法
      Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
      // 调用该过滤器
      List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
      

      上述的做法将使程序变得非常灵活,比如之后要判断List中是否包含长度大于5的等等,则只需要利用Predicate进行相应的实现即可。
      // 扩展
      Predicate nonEmptyStringPredicate1 = (String s) -> s.length() > 5;

    2. Consumer
      java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回,如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
      比如创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。

       // 底层源码
          @FunctionalInterface
          public interface Consumer<T> {
              void accept(T t);
          
              default Consumer<T> andThen(Consumer<? super T> after) {
                  Objects.requireNonNull(after);
                  return (T t) -> { accept(t); after.accept(t); };
              }
          }
          public static <T> void forEach(List<T> list, Consumer<T> c) {
              for (T i : list) {
                  c.accept(i);
              }
          }
          
          forEach(Arrays.asList(1,2,3,4,5),
                 	(Integer i) -> System.out.println(i));
      
    3. Function
      java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。

       // 底层源码
         @FunctionalInterface
         public interface Function<T, R> {
         
             R apply(T t);
         
             default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
                 Objects.requireNonNull(before);
                 return (V v) -> apply(before.apply(v));
             }
         
             default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
                 Objects.requireNonNull(after);
                 return (T t) -> after.apply(apply(t));
             }
         
             static <T> Function<T, T> identity() {
                 return t -> t;
             }
         }
      

      例如: 创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。

       public static <T,R> List<R> map(List<T> list, Function<T,R> f) {
           List<R> result = new ArrayList<>();
           for (T s: list) {
               result.add(f.apply(s));
           }
           return result;
       }
      
      // [7, 2, 6]
      List<Integer> l = map(Arrays.asList("lambdas","in","action"),
      					(String s) -> s.length());
      

      注意:任何函数式接口都不允许抛出受检异常,如果你需要在Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。

    方法引用

    方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
    
    先看一个例子: 
    
    // 之前
    inventory.sort((Apple a1, Apple a2)
    				-> a1.getWeight().compareTo(a2.getWeight()));
    
    // 现在(使用方法引用和 java.util.Comparator.comparing)
    inventory.sort(comparing(Apple::getWeight));
    

    基本思想:如果一个Lambda表达的只是"直接调用这个方法", 最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。

    // 方法引用就是Lambda的快捷写法
    Apple::getWeight
    | | 等价于
    (Apple a) -> a.getWeight()
    
    Lambda 等效的方法引用
    (Apple a) -> a.getWeight() Apple::getWeight
    () -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
    (str, i) -> str.substring(i) String::substring
    (String s) -> System.out.println(s) System.out::println

    如何创建方法引用

    方法引用主要有三类
    
    1. 指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt )。
    2. 指向任意类型实例方法的方法引用( 例 如 String 的 length 方 法 , 写 作String::length )。
    3. 指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensiveTransaction::getValue )。

    流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个		  实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
    
     public static void main(String[] args) {
                Apple a1 = new Apple("red", 500);
                Apple a2 = new Apple("black", 300);
                Apple a3 = new Apple("green", 420);
                // Java7
                List<Apple> appleList = Arrays.asList(a1, a2, a3);
                List<Apple> list = new ArrayList<>();
                for (Apple a : appleList) {
                    if (a.getWeight() > 400) {
                        list.add(a);
                    }
                }
        
                Collections.sort(list, new Comparator<Apple>() {
                    @Override
                    public int compare(Apple o1, Apple o2) {
                        return Integer.compare(o1.getWeight(), o2.getWeight());
                    }
                });
        
                List<String> colorList = new ArrayList<>();
                for(Apple a: list){
                    colorList.add(a.getColor());
                }
                System.out.println(colorList);
        
                // Java8
                List<String> colorList2 =
                            appleList.stream()
                                .filter(a -> a.getWeight() > 400)
                                .sorted(Comparator.comparing(Apple::getWeight).reversed())
                                .map(Apple::getColor)
                                .collect(Collectors.toList());
                System.out.println(colorList2);
        
                // 多核架构并行处理
                List<String> colorList3 =
                        appleList.parallelStream()
                                .filter(a -> a.getWeight() > 400)
                                .sorted(Comparator.comparing(Apple::getWeight).reversed())
                                .map(Apple::getColor)
                                .collect(Collectors.toList());
                System.out.println(colorList3);
            }
    

    Java8中的Stream API可以让你写出这样的代码: 声明性:更简洁,更易读。可复合:更灵活。可并行:性能更好。

    流到底是什么?

    简单来说就是支持数据处理操作的源生成的元素序列。

    1. 元素序列:可以访问特定元素类型的一组有序值。
    2. 源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
    3. 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以顺序执行,也可并行执行。
    4. 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
    5. 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

    使用流

    1. 一个数据源(如集合)来执行一个查询。
    2. 一个中间操作链,形成一条流的流水线。
    3. 一个终端操作,执行流水线,并能生成结果。

    中间操作

    操作 类型 返回类型 操作参数 函数描述符
    filter 中间 Stream Predicate T -> boolean
    map 中间 Stream Function<T,R> T -> R
    sorted 中间 Stream Comparator (T, T) -> int
    distinct 中间 Stream Predicate T -> boolean

    终端操作

    操作 类型 目的
    forEach 终端 消费流中的每个元素,并对其应用Lambda。这一操作返回void
    count 终端 返回流中的个数。这一操作返回long
    collect 终端 把流归约成一个集合,比如List,Map甚至是Integer

    小结

    * 流是“从支持数据处理操作的源生成的一些列元素”。
    * 流利用内部迭代:通过filter,map,sorted等操作被抽象掉了。
    * 流操作有两类:中间操作和终端操作。
    * filter和map等中间操作会返回一个流,并可以连接在一起。可以用他们来设置一条流水线,但不会生成任何结果。
    * forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
    * 流中的元素是按需计算的。
    

    使用谓词筛选

    	Stream接口支持filter方法,该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包含所有符合谓词的元素的流。
    

    筛选各异的元素

    	流还支持一个叫做distinct的方法, 会返回一个元素各异(根据流所生成元素hashCode和equals方法实现)的流。
    
    e.g. 下面代码会筛选出所有偶数,并确保没有重复。
    
    List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
    numbers.stream()
    		.filter(i -> i % 2 == 0)
    		.distinct()
    		.forEach(System.out::println);
    

    截短流

    流还支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。

    List<Dish> dishes = menu.stream()
    				.filter(d -> d.getCalories() > 300)
    				.limit(3)
    				.collect(toList());
    

    跳过元素

    流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中能够元素不足n个,则返回一个空流。注意:limit(n)和skip(n)是互补的!

    List<Dish> dishes = menu.stream()
    					.filter(d -> d.getCalories() > 300)
    					.skip(2)
    					.collect(toList());
    

    对流中每一个元素应用函数

    流还支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”,而不是去“修改”)。

    	e.g. 下面的代码把方法引用Dish::getName传给了map方法,来提取流中菜肴的名称。
    
    List<String> dishNames = menu.stream()
        					.map(Dish::getName)
        					.collect(toList());
    
    	如果你要找出每道菜的名称并判断名称有多长,怎么做?
    
    List<Integer> dishNameLengths = menu.stream()
        							.map(Dish::getName)
        							.mdistinctap(String::length)
        							.collect(toList);
    

    流的扁平化

    对于一张单词表,如何返回一张列表,列出里面各不相同的字符呢?e.g. 给定单词列表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。

    你可能想到的第一个版本是这样的

    words.stream()
    	.map(word -> word.split(""))
    	.distinct()
    	.collect(toList());
    
    	这个方法的问题在于:传递给map方法的Lambda为每个单词返回了一个String[]。因此,map返回的流实际上是Stream<String[]>类型的。
    

    检查谓词是否至少匹配一个元素

    	anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。
    
    	e.g.  检查菜单中是否有素食
    
    if (menu.stream().anyMatch(Dish::isVegetarian)) {
        System.out.println("The menu is (somewhat) vegetarian frinedly!!!")
    }
    
    	anyMatch方法返回一个boolean,因此是一个终端操作。
    

    检查谓词是否匹配所有元素

    	allMatch方法的工作原理和anyMatch类似,它会看看流中的元素是否都能匹配给定的谓词。
    
    	e.g. 查看菜品是否有利健康(即所有菜的热量都低于1000卡路里)
    
    boolean isHealthy = menu.stream()
    		 			.allMatch(d -> d.getCalories() < 1000);
    

    与之对应的是noneMatch,他可以确保流中没有任何元素与给定的谓词匹配。

    boolean isHealthy = menu.stream()
       					.noneMatch(d -> d.getCalories() >= 1000);
    

    anyMatch,allMatch和noneMatch这三个操作都用到了所谓的短路,这就是Java中&&和||运算符短路在流中的版本。

    查找元素

    	findAny方法将返回当前流中的任意元素。可以与其他流操作结合使用。
    
    	e.g. 想找一道素食菜肴,可以结合使用filter和findAny来查询。
    
    Optional<Dish> dish = 
        	menu.stream()
        	.filter(Dish::isVegetarian)
        	.findAny();
    

    Optional

    	Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的例子中,findAny可能什么元素都没找到,而Optional的引入可以避免出现null的问题。
    

    Optional中有几种可以迫使你显式地检查值是否存在或处理值不存在的方法。

    	isPresent():将在Optional包含值的时候返回true,否则返回false。
    
    	ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。
    
    	T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
    
    	T orElse(T other)会在值存在时返回值,否则返回一个默认值。
    
    menu.stream()
    	.filter(Dish::isVegetarian)
    	.findAny()
    	.ifPresent(d -> System.out.println(d.getName());
    

    查找第一个元素

    List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> firstSquareDivisibleByThree =
    	someNumbers.stream()
    				.map(x -> x * x)
    				.filter(x -> x % 3 == 0)
    				.findFirst(); // 9
    

    何时使用findFirst和findAny

    	答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
    

    归约

    	如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。
    
    展开全文
  • JAVA 8 '::' 关键字

    万次阅读 多人点赞 2018-06-05 16:17:28
    Java 8 中我们可以通过 `::` 关键字来访问类的构造方法,对象方法,静态方法。现有一个类 Somethingclass Something { // constructor methods Something() {} Something(String something) { System.out....
  • Java13都要来了,你还不了解Java8的新(旧)特性?

    千次阅读 多人点赞 2019-04-07 00:03:50
    官方版本虽然已经更新到Java12了,但是就目前来说,大多数Java系统还是运行在Java8上的,剩下一部分历史遗留系统还跑在Java7,甚至Java6上。我刚学Java的时候,正好处于Java7版本末期,彼时已经有很多关于Java8新...
  • java8 (一):为神马要学习java8?

    千次阅读 2018-08-02 00:10:06
    1. java8新特性 1.1 新特性介绍: 简而言之,java8的新特性就是:Lamdba函数(匿名函数),流,默认方法。 Java8 的灵活使用,会使得代码可读性更好(前提是你的同事也使用,别人不会,你强行使用,会被喷的!!!)...
  • Java 8的新特性—终极版

    万次阅读 多人点赞 2016-03-15 15:38:14
    前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API ...
  • Java8 64位免安装版

    2020-07-30 23:31:53
    jdk1.8免安装版本,只要配置上环境变量即可使用,开发工具中也可直接配置路径
  • Java8——优化

    千次阅读 2019-03-22 09:52:23
    对HashMap等的优化   在JDK1.7及以前的版本中,HashMap的数据结构是数组+链表,在往HashMap中存放数据对象的时候,会先根据哈希算法对该对象进行计算,得出该对象的哈希码,再根据哈希码计算该对象在HashMap数组中...
  • Java 8、9、10、11 新特性

    万次阅读 2018-10-16 08:54:50
    不知不觉Java已经发布到了第11版,兄弟们恐怕还不知道他们的新特性吧。快来一起瞧瞧吧。 Java 8 1.Lambdas表达式与Functional接口 2.接口的默认与静态方法 ...详细网址:Java8 新特性   Java 9 ...
  • Java 14 都快来了,为什么还有这么多人固守Java 8

    万次阅读 多人点赞 2020-01-03 05:19:27
    Java 9开始,Java版本的发布就让人眼花缭乱了。每隔6个月,都会冒出一个新版本出来,Java 10 , Java 11, Java 12, Java 13, 到2020年3月份,...
  • ubuntu16.04下安装Java8详细教程

    万次阅读 多人点赞 2018-10-06 10:34:24
    首先在Oracle官网下载java8的linux-64安装包:jdk-8u181-linux-x64.tar.gz (1) 将安装包拷贝到/opt目录下 ​ sudo cp jdk-8u181-linux-x64.tar.gz /opt (2) 在/opt目录下创建java目录,并且将所有权限更改为当前...
  • Java8零基础入门视频教程

    万人学习 2019-01-15 13:26:50
    这门课程基于主流的java8平台,由浅入深的详细讲解了java SE的开发技术,可以使java方向的入门学员,快速扎实的掌握java开发技术!
  • Java Swing 图形界面开发(目录)

    万次阅读 多人点赞 2020-08-13 17:40:37
    Java Swing 图形界面(GUI)开发基础教程(目录)
  • Java8深入分析

    万人学习 2018-11-22 10:10:51
    Java语言进阶视频教程,该课程针对有一定Java基础的学员,详细讲解Java多线程、XML、Socket、Swing、IO流、反射、注解,还有传值与传址的区别 、递归算法等,使学员熟练掌握JavaSE技术,轻松应对职场挑战。
  • Homebrew安装java8

    万次阅读 2018-09-18 23:24:16
    on't rely on Oracle to install Java properly on your Mac. Use Homebrew. this will install the latest jdk: brew cask install java If you want to manage multiple versions of Java on y...
  • Java8 实战系列-01-序章

    万次阅读 2019-03-26 22:17:26
    Java8 实战系列-01-序章 Java8 实战系列-02-lambda 表达式简介 Java8 实战系列-03-lambda 表达式实战 Java8 实战系列-04-lambda 内置函数 Java8 实战系列-05-lambda 类型推断 Java8 实战系列-06-lambda 方法...
  • Java之路

    万人学习 2020-07-21 19:58:19
    你了解Java吗?你知道Java能做什么吗?你知道Java该怎么学吗?你知道Java未来的发展趋势、发展状况吗?
  • JAVA API中文在线帮助文档

    万次阅读 多人点赞 2019-06-25 11:23:18
    https://www.w3cschool.cn/java/dict http://www.matools.com/api/java8 http://tool.oschina.net/apidocs/api http://www.javaweb.cc/help/JavaAPI1.6/overview-summary.html
1 2 3 4 5 ... 20
收藏数 7,482,755
精华内容 2,993,102
关键字:

java8