精华内容
下载资源
问答
  • JAVA8新特性

    万次阅读 2020-04-02 11:21:04
    Java 8使我们能够通过使用default关键字向接口添加非抽象方法实现。 此功能也称为虚拟扩展方法。 第一个例子: interface Formula{ double calculate(int a); default double sqrt(int a) { ret...

    接口的默认方法(Default Methods for Interfaces)

    Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现。 此功能也称为虚拟扩展方法

    第一个例子:

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

    Formula 接口中除了抽象方法计算接口公式还定义了默认方法 sqrt。 实现该接口的类只需要实现抽象方法 calculate。 默认方法sqrt 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。

    public class Main {
    
      public static void main(String[] args) {
        // TODO 通过匿名内部类方式访问接口
        Formula formula = new Formula() {
            @Override
            public double calculate(int a) {
                return sqrt(a * 100);
            }
        };
    
        System.out.println(formula.calculate(100));     // 100.0
        System.out.println(formula.sqrt(16));           // 4.0
    
      }
    
    }

    formula 是作为匿名对象实现的。该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)。在下一节中,我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。

    译者注: 不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。

    Lambda表达式(Lambda expressions)

    首先看看在老版本的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关键字,但是你还可以写得更短点:

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

    List 类本身就有一个 sort 方法。并且Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还有什么其他用法。

    函数式接口(Functional Interfaces)

    译者注: 原文对这部分解释不太清楚,故做了修改!

    Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。 像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable 与 java.util.concurrent.Callable 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用@FunctionalInterface 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示

    @FunctionalInterface 注解

    示例:

    @FunctionalInterface
    public interface Converter<F, T> {
      T convert(F from);
    }
        // TODO 将数字字符串转换为整数类型
        Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
        Integer converted = converter.convert("123");
        System.out.println(converted.getClass()); //class java.lang.Integer

    译者注: 大部分函数式接口都不用我们自己写,Java8都给我们实现好了,这些接口都在java.util.function包里。

    方法和构造函数引用(Method and Constructor References)

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

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

    Java 8允许您通过::关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法:

    class Something {
        String startsWith(String s) {
            return String.valueOf(s.charAt(0));
        }
    }
    Something something = new Something();
    Converter<String, String> 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方法的参数类型来选择合适的构造函数。

    Lamda 表达式作用域(Lambda Scopes)

    访问局部变量

    我们可以直接在 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上使用的。

    Predicates

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

    译者注: Predicate 接口源码如下

    package java.util.function;
    import java.util.Objects;
    
    @FunctionalInterface
    public interface Predicate<T> {
        
        // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
        boolean test(T t);
    
        //and方法与关系型运算符"&&"相似,两边都成立才返回true
        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);
        }
        //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true
        default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
        }
       // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).
        static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
        }

    示例:

    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();

    Functions

    Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):

    译者注: Function 接口源码如下

    package java.util.function;
     
    import java.util.Objects;
     
    @FunctionalInterface
    public interface Function<T, R> {
        
        //将Function对象应用到输入的参数上,然后返回计算结果。
        R apply(T t);
        //将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。
        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;
        }
    }
    Function<String, Integer> toInteger = Integer::valueOf;
    Function<String, String> backToString = toInteger.andThen(String::valueOf);
    backToString.apply("123");     // "123"

    Suppliers

    Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。

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

    Consumers

    Consumer 接口表示要对单个输入参数执行的操作。

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

    Comparators

    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

    Optionals

    Optionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。

    Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。

    译者注:示例中每个方法的作用已经添加。

    //of():为非null的值创建一个Optional
    Optional<String> optional = Optional.of("bam");
    // isPresent(): 如果值存在返回true,否则返回false
    optional.isPresent();           // true
    //get():如果Optional有值则将其返回,否则抛出NoSuchElementException
    optional.get();                 // "bam"
    //orElse():如果有值则将其返回,否则返回指定的其它值
    optional.orElse("fallback");    // "bam"
    //ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
    optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

    推荐阅读:[Java8]如何正确使用Optional

    Streams(流)

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

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

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

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

    Filter(过滤)

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

            // 测试 Filter(过滤)
            stringList
                    .stream()
                    .filter((s) -> s.startsWith("a"))
                    .forEach(System.out::println);//aaa2 aaa1

    forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。

    Sorted(排序)

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

            // 测试 Sort (排序)
            stringList
                    .stream()
                    .sorted()
                    .filter((s) -> s.startsWith("a"))
                    .forEach(System.out::println);// aaa1 aaa2

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

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

    Map(映射)

    中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。

    下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

            // 测试 Map 操作
            stringList
                    .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 类型的值。

            // 测试 Match (匹配)操作
            boolean anyStartsWithA =
                    stringList
                            .stream()
                            .anyMatch((s) -> s.startsWith("a"));
            System.out.println(anyStartsWithA);      // true
    
            boolean allStartsWithA =
                    stringList
                            .stream()
                            .allMatch((s) -> s.startsWith("a"));
    
            System.out.println(allStartsWithA);      // false
    
            boolean noneStartsWithZ =
                    stringList
                            .stream()
                            .noneMatch((s) -> s.startsWith("z"));
    
            System.out.println(noneStartsWithZ);      // true

    Count(计数)

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

          //测试 Count (计数)操作
            long startsWithB =
                    stringList
                            .stream()
                            .filter((s) -> s.startsWith("b"))
                            .count();
            System.out.println(startsWithB);    // 3

    Reduce(规约)

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

            //测试 Reduce (规约)操作
            Optional<String> reduced =
                    stringList
                            .stream()
                            .sorted()
                            .reduce((s1, s2) -> s1 + "#" + s2);
    
            reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2

    译者注: 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于Integer sum = integers.reduce(0, (a, b) -> a+b);也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

    // 字符串连接,concat = "ABCD"
    String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
    // 求最小值,minValue = -3.0
    double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
    // 求和,sumValue = 10, 有起始值
    int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
    // 求和,sumValue = 10, 无起始值
    sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
    // 过滤,字符串连接,concat = "ace"
    concat = Stream.of("a", "B", "c", "D", "e", "F").
     filter(x -> x.compareTo("Z") > 0).
     reduce("", String::concat);

    上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。更多内容查看: IBM:Java 8 中的 Streams API 详解

    Parallel 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());
    }

    我们分别用串行和并行两种方式对其进行排序,最后看看所用时间的对比。

    Sequential Sort(串行排序)

    //串行排序
    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));
    1000000
    sequential sort took: 709 ms//串行排序所用的时间
    

    Parallel Sort(并行排序)

    //并行排序
    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));
    
    1000000
    parallel sort took: 475 ms//串行排序所用的时间

    上面两个代码几乎是一样的,但是并行版的快了 50% 左右,唯一需要做的改动就是将 stream() 改为parallelStream()

    Maps

    前面提到过,Map 类型不支持 streams,不过Map提供了一些新的有用的方法来处理一些日常任务。Map接口本身没有可用的 stream()方法,但是你可以在键,值上创建专门的流或者通过 map.keySet().stream(),map.values().stream()map.entrySet().stream()

    此外,Maps 支持各种新的和有用的方法来执行常见任务。

    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));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9

    putIfAbsent 阻止我们在null检查时写入额外的代码;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(日期相关API)

    Java 8在 java.time 包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似,但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。

    译者注(总结):

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

    • 在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId(在java.time包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds的静态方法,它返回所有区域标识符。

    • jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

    Clock

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

    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();
    System.out.println(millis);//1552379579043
    Instant instant = clock.instant();
    System.out.println(instant);
    Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z
    System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019

    Timezones(时区)

    在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId(在java.time包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds的静态方法,它返回所有区域标识符。

    //输出所有区域标识符
    System.out.println(ZoneId.getAvailableZoneIds());
    
    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]
    System.out.println(zone2.getRules());// 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();//获取现在的日期
    System.out.println("今天的日期: "+today);//2019-03-12
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
    System.out.println("明天的日期: "+tomorrow);//2019-03-13
    LocalDate yesterday = tomorrow.minusDays(2);
    System.out.println("昨天的日期: "+yesterday);//2019-03-11
    LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
    System.out.println("今天是周几:"+dayOfWeek);//TUESDAY

    从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单,下面是使用 DateTimeFormatter 解析字符串的例子:

        String str1 = "2014==04==12 01时06分09秒";
            // 根据需要解析的日期、时间字符串定义解析所用的格式器
            DateTimeFormatter fomatter1 = DateTimeFormatter
                    .ofPattern("yyyy==MM==dd HH时mm分ss秒");
    
            LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1);
            System.out.println(dt1); // 输出 2014-04-12T01:06:09
    
            String str2 = "2014$$$四月$$$13 20小时";
            DateTimeFormatter fomatter2 = DateTimeFormatter
                    .ofPattern("yyy$$$MMM$$$dd HH小时");
            LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2);
            System.out.println(dt2); // 输出 2014-04-13T20:00
    

    再来看一个使用 DateTimeFormatter 格式化日期的示例

    LocalDateTime rightNow=LocalDateTime.now();
    String date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow);
    System.out.println(date);//2019-03-12T16:26:48.29
    DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
    System.out.println(formatter.format(rightNow));//2019-03-12 16:26:48

    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是不可变的,所以它是线程安全的。 关于时间日期格式的详细信息在这里

    Annotations(注解)

    在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 {}
    展开全文
  • Java8新特性

    千次阅读 2016-05-26 11:30:11
    Java8新特性

    原文出处:http://blog.decaywood.me/about/#zh


    整个教程将详细的介绍Java8所有的新特性,并以简短的代码展示出来。你将学会如何使用默认接口方法/lambda表达式/方法引用以及@Repeatable注解。读完本文后,你将对流(stream)/函数式接口(functional interface)/map拓展以及新的日期API等最新的API变动有一个相对了解。整个教程大部分都是以代码形式体现,没有过多的文字说明,让我们开始吧:)

    接口默认方法

    Java8可以让我们通过default关键字在接口中添加非抽象的方法,这是一个新的概念,虚拟拓展方法(补充:这样可以提供声明行为的默认实现)

    话不多说,上代码:

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

    除了calculate抽象方法,Formula接口同时定义了默认方法sqrt。这样一来,接口实现类只需要实现抽象方法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接口由匿名对象实现。可以看出,代码非常冗长,仅仅简单的实现了sqrt(a * 100)计算就用了6行代码。我们将在下一章节看到如何在Java8中用更简洁的方式实现单个抽象方法的接口。

    以下为补充:

    Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:

    public interface DefaulableFactory {
        // Interfaces now allow static methods
        static double calculate(int a) {
            return sqrt(a * 100);
        }
    }
    

    在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……

    尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档

    Lambda表达式

    让我们以一个简单老版本的字符串排序为例子开始这一章节:

    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和一个比较器来对List里面的元素进行排序,你可能经常遇到这种需要传入一个匿名比较器给排序方法的情况。

    除了创建一个匿名对象的方式来实现Comparator接口之外,Java8提供了一个更加简洁的语法,lambda表达式:

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

    很明显,代码明显简洁了许多而且表达更加清晰了,但它还可以简化:

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

    对于只有一行的方法,可以采取去掉外围方法体和返回值的方式进一步简化:

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

    List现在新增了sort方法,并且编译器可以自动分析出形参类型,所以你可以直接省略形参类型。接下来,我们会更加深入探索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注解不是必须的,如果省略,代码依然是有效的。

    方法引用和构造器引用

    如果使用静态方法引用,上文中的例子能够得到更进一步的简化。

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

    Java8允许你通过使用::关键字来传递方法和构造器的引用。上面的例子展示了如何引用一个静态方法。不过我们也可以引用实例方法:

    class Something {
        String startsWith(String s) {
            return String.valueOf(s.charAt(0));
        }
    }
    
    Something something = new Something();
    Converter<String, String> converter = something::startsWith;
    String converted = converter.convert("Java");
    System.out.println(converted);    // "J"
    

    让我们看看::关键字是如何引用构造器的。首先我们定义一个有不同构造器的Person类:

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

    接下来我们定义一个Person工厂接口用于创建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接口中定义的方法签名即PersonFactory.create

    Lambda 作用域

    从lambda表达式访问外部作用域的变量和匿名对象有点类似。你可以从本地外部作用域访问带有final修饰符的变量,也可以访问实例变量和静态变量。

    访问本地变量

    我们可以从lambda表达式的外部作用域读取本地final变量:

    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,但是隐式地必须为不变量才能通过编译检查,下面的代码就不能通过编译,因为num值被修改了:

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

    同时,从lambda表达式赋值给num也是禁止的。(补充:对于这种行为很好理解,本地变量存储在栈中,lambda表达式本质上还是对象,存储在堆中,必然不能对栈数据进行写操作。)

    访问域变量和静态变量

    相比本地变量,对于实例变量和静态变量,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表达式中则不能调用默认方法。下面的代码不能通过编译:

    Formula formula = (a) -> sqrt( a * 100);
    

    内置函数式接口

    JDK1.8的API囊括了许多内置函数式接口。其中一些在老版本中就被广泛使用(补充:还记得上文说过的吗?只要接口中定义了唯一的抽象方法就符合函数式接口的规范,可以被当做函数式接口。),例如Comparator或者Runnable。这些老版本就存在的接口在新版本中经过@FunctionalInterface注解标记进行拓展以提供lambda支持。

    除了这些接口,Java8 API也提供了全新的函数式接口来减轻你的工作负担。其中一些接口借鉴了Google Guava库中被广泛使用的功能。即使你对Guava了如指掌,你也应该认真地了解一下Java8中提供的新接口拓展了哪些有用的方法。

    Predicates

    Predicate接口是一个返回boolean值的接口,它接收一个参数。这个接口包含了各种默认方法,可以对Predicates进行各种组合来完成复杂的逻辑运算(and, or, negate)。

    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();
    

    Functions

    Function接口接收一个参数,返回一个结果。其包含的默认方法能用于将多个Functions结构链接起来(compose,andthen)

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

    Suppliers

    Supplier接口根据给定的泛型返回一个结果。不像Functions接口,Suppliers不需要传入参数。

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

    Consumers

    Consumer接口代表一类只接收一个参数的操作。

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

    Comparators

    Comparator接口在老版本JDK中就广泛使用。Java8新引入了许多默认方法到接口中。

    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
    

    Optionals

    Optional虽然不属于函数式接口,但它是防止空指针错误(NullPointerException)的利器。Optional对于接下来的内容来说是一个非常重要的概念,所以,让我们先大概地了解一下Optional是如何工作的。

    Optional是一个存储值的简单容器,只有null和non-null两种概念。想象一个方法,它有可能返回一个非空的结果,但也有可能什么都不返回(补充:例如查找某个用户,但是并没有这个用户的相关信息,null会造成空指针错误,所以返回null按道理是不合适的)。在Java8中,你这时就可以返回Optional来替代null了。

    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"
    

    Streams

    java.util.Stream代表一系列可以进行一种或多种操作的元素,Stream也叫做流。流操作既可以是intermediate(补充:即还有下一步操作)也可以是terminal(终止)的。terminal操作返回一个特定类型的结果。intermediate操作则返回流对象自身以进行链式调用。流由source(数据源)创建,例如集合(java.util.Collection)中的List/Set(map不支持流操作)。流操作既可以顺序地执行也可以并行执行(补充:这里指并行流)。

    有需要的可以看一看Stream.js,一个跟Java8 Stream API风格相似的JavaScript API。

    让我们看看顺序流是如何工作的。首先我们创建一个String列表作为Stream的示例源:

    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");
    

    Collections在Java8中进行了拓展,所以你可以通过调用Collection.stream()或者Collection.parallelStream()很方便地生成流。接下来的章节中将会介绍最常用的一些流操作。

    Filter

    Filter接收一个Predicate类型的参数来对流中的所有元素进行过滤操作。Filter操作属于intermediate操作,可以让我们在输出结果后进一步调用其他流操作(forEach)。ForEach操作接收一个Consumer类型的参数来处理经过Filter操作后的流的元素。ForEach属于terminal操作。它没有返回值,所以我们不能继续流操作。

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

    Sorted

    Sorted操作属于intermediate操作,它返回一个排好序的流的视图。如果不传入自定义的Comparator,元素将以默认排序规则排序。

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

    注意,sorted操作只是创建一个排好序的流的视图而不是操作内部隐藏的集合元素顺序。stringCollection的顺序被没有被篡改:

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

    Map

    intermediate操作map传入Function对象将流中的每个元素转换为另一个对象。下面的例子演示了将每个String对象转换为大写的String对象。你也可以使用map操作来将流中的元素转换为其他类型的对象。返回的流的泛型类型依赖于你传给map的Function对象的泛型类型(补充:你不需要显式指定泛型类型,Java8的编译器相当智能,可以自动推导出参数类型)。

    stringCollection
        .stream()
        .map(String::toUpperCase)
        .sorted((a, b) -> b.compareTo(a))
        .forEach(System.out::println);
    
    // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
    

    Match

    不同的match操作可以用于判断特定的predicate是否匹配流中的元素。所有这种类型的操作都属于terminal操作,以返回一个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

    Count属于terminal操作,返回流中的元素个数,类型为long。

    long startsWithB =
        stringCollection
            .stream()
            .filter((s) -> s.startsWith("b"))
            .count();
    
    System.out.println(startsWithB);    // 3
    

    Reduce

    这种terminal操作将会借助传入的Function类型参数对流中的元素进行归一化。结果为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"
    

    并行流

    在流那一节中提到过,流既可以为顺序的也可以是并行的。顺序流中的操作在单个线程中执行,而并行流中的操作则是多线程并发执行。

    下面的例子将演示使用并行流提升性能有多简单。

    首先我们创建一个存储着许多不同的数据的列表:

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

    现在我们测试一下对这个集合进行排序操作所消耗的时间。

    Sequential Sort

    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));
    
    // sequential sort took: 899 ms
    

    Parallel Sort

    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));
    
    // parallel sort took: 472 ms
    

    可以看出,两段代码基本一样,但并行流的排序时间几乎快了50%。而你只需要把stream()改成parallelStream()而已。

    (补充:并行流的底层实现为ForkJoin框架,底层工作线程数可通过设值系统属性来进行定制,例如System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”, 4),这样就可以根据自己业务是计算密集型还是IO密集型来采取不同的配置,使CPU利用率最大化。)

    Maps

    上文中提到过,Map类型不直接支持Stream操作。在Map接口中没有可用的Stream()方法。然而你可以通过map.keySet().stream()map.values().stream()map.entrySet().stream()在Map的键/值或者Entry上创建特殊的流。

    此外,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可以让我们避免额外的添加null检查;forEach接收一个Consumer对象来对map中的值进行操作。

    下面的例子展示了如何在map中利用Function对象进行计算(补充:这里用到了重载方法,map会将计算结果置入value中):

    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
    

    接下来,我们将学会如何根据给定的key移除entries,只有map中有匹配的entry才会进行移除动作:

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

    另外一个有用的方法(如果map中对应的key没有对应值则指定一个默认值):

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

    在map中合并entries变得相当简单:

    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
    

    合并操作会在key对应的entry不存在时将新的值置入,否则进行合并操作后再置入合并结果。

    日期 API

    Java8包含了全新的日期和时间API,他们放在java.time包下面。新的日期API功能可与Joda-Time相比,然而他们还是存在一些差异。下面的例子将会介绍新API中最重要的一些特性。

    Clock

    Clock可以访问当前的日期和时间。它可以处理时区问题,也可以用于代替System.currentTimeMillis()来获取从Unix EPOCH(1970-01-01 00:00:00 UTC)到现在的毫秒数。而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

    Timezones由ZoneId表示。它们能通过静态工厂方法返回。Timezones定义了偏移量,这对于Instant实例和本地日期(local dates)和时间之间的转换非常重要:

    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表示本地时间,省略了时区因素。例如10pm 或者 17:30:15。下面的例子用上面定义的Timezone对象创建了两个LocalTime对象。下面我们将比较两个时间,并计算两个时间在时和分上的差。

    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类似。下面的例子演示了如何通过增减天数/月数/年数来计算新的日期。注意,每次操作都返回了一个新的实例。

    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
    

    把String对象解析为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
    

    利用TimeZone对象提供的信息,它可以转换为一个Instant对象。Instant对象可以很容易的转换为遗留类型java.util.Data

    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对象和上述LocalDate等类似。除了使用标准的Formatter,我们还可以进行自定义。

    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是不可变且线程安全的。
    可以在这里查看更多模式的语法细节。

    注解

    注解在Java8中是可以重复定义的。让我们用例子直观地理解新的注解。
    首先,我们定义了一个注解集,它包含一个注解数组:

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

    java8允许我们使用多个相同类型的注解标注同一个位置,不过注解定义时必须使用@Repeatable进行标注。

    对比1:使用容器注解(container annotation) – 旧版用法

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

    对比2:使用容器注解(container annotation) – 新版用法

    @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
    

    在对比2中,虽然我们没有在Person类声明@Hints注解,但是通过getAnnotation(Hints.class)任然可以读出数据。然而,更方便的方式是getAnnotationsByType(Hint.class),通过这种方法,我们可以直接访问所有标注的@Hint注解。

    此外,Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:

    public class Annotations {
        @Retention( RetentionPolicy.RUNTIME )
        @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
        public @interface NonEmpty {
        }
    
        public static class Holder< @NonEmpty T > extends @NonEmpty Object {
            public void method() throws @NonEmpty Exception {
            }
        }
    
        @SuppressWarnings( "unused" )
        public static void main(String[] args) {
            final Holder< String > holder = new @NonEmpty Holder< String >();
            @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
        }
    }
    
    展开全文
  • java8新特性

    千次阅读 2018-04-01 21:58:47
    Java8新特性(十大) Java8新特性包含语言、编译器、库、工具和JVM等方面的十多个新特性。 1.新特性 1.1.Java语言的新特性 1.1.1.Lambda表达式和函数式接口 1.1.2.接口的默认方法和静态方法 1.1.3.方法引用 ...

    Java8新特性(十大)
    Java8新特性包含语言、编译器、库、工具和JVM等方面的十多个新特性。
    1.新特性
    1.1.Java语言的新特性
    1.1.1.Lambda表达式和函数式接口
    1.1.2.接口的默认方法和静态方法
    1.1.3.方法引用
    1.1.4.重复注解
    1.2.Java编译器的新特性
    1.2.1.参数名称
    1.3.Java官方库的新特性
    1.3.1.Optional
    https://blog.csdn.net/canot/article/details/52956230
    1.3.2.Streams(代替for循环)
    1.3.3.Date/Time API
    1.3.4.JavaScript引擎Nashorn
    1.3.5.Base64
    1.3.6.并行数组
    1.3.7.并发性
    1.4.新的Java工具
    1.4.1.Nashorn引擎:jjs
    1.4.2.类依赖分析器:jdeps

    参考资料:
    https://blog.csdn.net/u014470581/article/details/54944384
    https://www.cnblogs.com/yw0219/p/7302597.html
    https://www.cnblogs.com/pkufork/p/java_8.html

    2.Lambda表达式
    2.1.Lambda表达式语法结构
    2.1.1.标准语法结构
    (Type1 param1, Type2 param2, …, TypeN paramN) -> {
    statment1;
    statment2;
    //………….
    return statmentM;
    }

    2.1.2.单参数语法结构
    当lambda表达式的参数个数只有一个,可以省略小括号
    2.1.3.单语句写法
    当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号
    2.1.4.方法引用写法
    Class or instance :: method
    2.2.lambda表达式的本质
    Lambda表达式本质上是一个匿名方法。
    例如:
    public int add(int x, int y) {
    return x + y;
    }

    转成Lambda表达式后是这个样子:
    (int x, int y) -> x + y;
    参数类型也可以省略,Java编译器会根据上下文推断出来:
    (x, y) -> x + y; //返回两数之和
    或者
    (x, y) -> { return x + y; } //显式指明返回值
    可见Lambda表达式有三部分组成:参数列表,箭头(->),以及一个表达式或语句块。

    将函数当成参数传递给某个方法

    3.函数式接口
    函数式接口就是只显式声明一个抽象方法的接口。为保证方法数量不多不少,java8提供了一个专用注解@FunctionalInterface,这样,当接口中声明的抽象方法多于或少于一个时就会报错。如下图所示:

    4.接口的默认方法与静态方法
    4.1.接口的默认方法
    可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。
    4.2.接口的静态方法
    可以在接口中定义静态方法,使用static关键字,也可以提供实现。

    举例:
    接口定义
    package java8;

    public interface InterfaceFunctional {

    // static修饰符定义静态方法  
    static void staticMethod() {  
        System.out.println("InterfaceFunctional接口中的静态方法");  
    }  
    
    // default修饰符定义默认方法  
    default void defaultMethod() {  
        System.out.println("InterfaceFunctional接口中的默认方法");  
    }  
    

    }

    实现类
    package java8;

    public class InterfaceFunctionalImpl implements InterfaceFunctional {

    }

    测试类:
    package java8;

    public class Test {

    public static void main(String[] args) {
        InterfaceFunctionalImpl impl = new InterfaceFunctionalImpl();
        impl.defaultMethod();
    
        InterfaceFunctional.staticMethod();
    }
    

    }

    输出结果:
    InterfaceFunctional接口中的默认方法
    InterfaceFunctional接口中的静态方法

    5.方法引用
    举例:
    package java8;

    import java.util.function.Supplier;

    public class FunctionalReference {

    public static FunctionalReference create(final Supplier<FunctionalReference> supplier) {
        return supplier.get();
    }
    
    public static void collide(final FunctionalReference reference) {
        System.out.println("Collided " + reference.toString());
    }
    
    public void follow(final FunctionalReference another) {
        System.out.println("Following the " + another.toString());
    }
    
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
    

    }

    方法引用举例:
    package java8;

    import java.util.Arrays;
    import java.util.List;

    public class FunctionalReferenceTest {

    public static void main(String[] args) {
        // 第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class<T>::new。注意:这个构造器没有参数。
        final FunctionalReference reference = FunctionalReference.create( FunctionalReference::new );
        final List< FunctionalReference > references = Arrays.asList( reference );
    
        // 第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个FunctionalReference类型的参数。
        references.forEach( FunctionalReference::collide );
    
        // 第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
        references.forEach( FunctionalReference::repair );
    
        // 第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
        final FunctionalReference reference2 = FunctionalReference.create( FunctionalReference::new );
        references.forEach( reference2::follow );
    }
    

    }

    6.Streams
    是一种迭代器
    https://blog.csdn.net/u010425776/article/details/52344425
    6.1.流的获取
    // 集合
    List list = new ArrayList();
    Stream stream = list.stream();

        // 数组 
        String[] names = {"chaimm","peter","john"};
        Stream<String> stream2 = Arrays.stream(names);
    
        // 值
        Stream<String> stream3 = Stream.of("chaimm","peter","john");
    
        // 文件
        try {
            Stream lines = Files.lines(Paths.get("文件路径名"),Charset.defaultCharset());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    

    6.2.使用:
    List list1 = Arrays.asList(1,2,3,4);
    Stream stream4 = list1.stream();
    stream4.map(i -> i+1).forEach(System.out::println);

        List<String> alpha = Arrays.asList("a", "b", "c", "d");
        //Before Java8
        List<String> alphaUpper = new ArrayList<>();
        for (String s : alpha) {
            alphaUpper.add(s.toUpperCase());
        }
    
        System.out.println(alpha); //[a, b, c, d]
        System.out.println(alphaUpper); //[A, B, C, D]
    
        // Java 8
        List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(collect); //[A, B, C, D]
    
        // Extra, streams apply to any data type.
        List<Integer> num = Arrays.asList(1,2,3,4,5);
        List<Integer> collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());
        System.out.println(collect1); //[2, 4, 6, 8, 10]
    
    展开全文
  • Java8 新特性

    千次阅读 2020-01-12 20:29:10
    Java 8 (又称为 jdk 1.8 ) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,的 JavaScript 引擎,的日期 API,的 Stream API 等。

    本文内容如有错误、不足之处,欢迎技术爱好者们一同探讨,在本文下面讨论区留言,感谢。

    简述

    Java 8 (又称为 jdk 1.8 ) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。

    java8

    新功能

    列表
    1. Iterable 接口中的 forEach() 方法
    2. 接口中的默认方法和静态方法
    3. 功能接口和 Lambda 表达式
    4. Stream API 操作集合批量数据
    5. Date Time API 加强对日期与时间的处理
    6. 集合 API 改进
    7. 并发 API 改进
    8. Optional 类,解决空指针问题
    9. 新工具 新的编译工具
    详细介绍
    1. Iterable 接口中的 forEach() 方法

    当程序需要遍历 Collection 时,将会创建一个 Iterator 其目的是进行迭代集合的全部对象,然后针对 Collection 中的每个元素将业务逻辑循环联系在一起。如果迭代器使用不正确,程序会抛出 ConcurrentModificationException

    Java 8 在接口中引入了 forEach 方法,java.lang.Iterable 因此在编写代码时,开发人员仅需要关注业务逻辑即可。forEach 方法将 java.util.function.Consumer 对象作为参数,因此有助于编写可重用的业务逻辑代码。

    package iterable.java8;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    public class IterableDemo {
        public static void main(String[] args) {
    
            // 创建集合
            List<Integer> myList = new ArrayList<Integer>();
            for(int i=0; i<10; i++) myList.add(i);
    
            // 使用 迭代器 iterator
            Iterator<Integer> it = myList.iterator();
            while(it.hasNext()){
                Integer i = it.next();
                System.out.println("Iterator Value::"+i);
            }
    
            // 使用forEach表达式
            myList.forEach(t -> System.out.println("forEach anonymous class Value::"+t));
        }
    
    }
    

    使用 java.util.function.Consumer

    package iterable.java8;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.function.Consumer;
    
    public class IterableConsumerDemo {
        public static void main(String[] args) {
    
            // 创建集合
            List<Integer> myList = new ArrayList<Integer>();
            for(int i=0; i<10; i++) myList.add(i);
    
            // 使用 迭代器 iterator
            Iterator<Integer> it = myList.iterator();
            while(it.hasNext()){
                Integer i = it.next();
                System.out.println("Iterator Value::"+i);
            }
    
            // 使用forEach表达式,使用 Consumer
            myList.forEach(new Consumer<Integer>() {
    
                public void accept(Integer t) {
                    System.out.println("forEach anonymous class Value::"+t);
                }
    
            });
    
            // 执行 子定义Consumer
            MyConsumer action = new MyConsumer();
            myList.forEach(action);
    
        }
    
    }
    
    // 实现 Consumer 接口
    class MyConsumer implements Consumer<Integer> {
    
        public void accept(Integer t) {
            System.out.println("Consumer Value::"+t);
        }
    }
    

    Consumer 接口源码:

    /**
     * 接受单个输入参数且不返回结果的操作
     * @since 1.8
     */
    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * 对给定参数执行此操作。
         *
         * @param t 输入参数,是个泛型
         */
        void accept(T t);
    
        /**
         * 对给定参数执行此操作。
         * 返回一个组合的 Consumer ,该组合 Consumer 将会依次执行accept方法定义的操作和 after 参数的 accept 操作。
         * 如果执行任何一个操作都会引发异常,则会将该异常抛出到调用该组合操作的调用方。
         * 如果执行此操作引发异常,不执行 after  参数的 accept 操作。
         *
         * @param after 当前对象执行accept操作后执行after中的accept操作  接口的默认实现
         */
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    

    Iterable 源代码

    public interface Iterable<T> {
    
        Iterator<T> iterator();
    
        default void forEach(Consumer<? super T> action) {
            Objects.requireNonNull(action);
            for (T t : this) {
                action.accept(t);
            }
        }
    
        default Spliterator<T> spliterator() {
            return Spliterators.spliteratorUnknownSize(iterator(), 0);
        }
    }
    
    2. 接口中的默认方法和静态方法

    仔细阅读上面的源码,可以很容易发现 forEach() 方法是在 Iterable 接口中定义的,但是开发人员都很清楚接口不能具有方法主体。从 Java 8 开始,接口已增强为具有实现的方法。使用 defaulestatic 关键字来创建带有方法实现的接口。

    Java 不能在 Class 中提供多重继承,因为它会导致 Diamond 问题。由于接口现在类似于抽象类,因此现在如何使用接口处理它。在这种情况下,编译器将引发异常,解决方案:在实现接口的类中提供实现逻辑。

    定义一个接口 Interface1

    package iterable.java8;
    
    @FunctionalInterface
    public interface Interface1 {
    
        default void doSomething(String str){
            System.out.println("I1 doSomething::"+str);
        }
    
    }
    
    

    接口 Interface2

    package iterable.java8;
    
    @FunctionalInterface
    public interface Interface2 {
    
        default void doSomething(String str){
            System.out.println("I2 doSomething::"+str);
        }
    
    }
    

    实现接口 Interface1Interface2

    package iterable.java8;
    
    public class InterfaceImplementDemo implements Interface1,Interface2 {
        @Override
        public void active(String str) {
    
        }
    
        /**
         * 必须实现 log 这个方法
         * @param str
         */
        @Override
        public void doSomething(String str) {
           System.out.println("Implement doSomething::"+str);
        }
    }
    

    Java 8 在 Collection API 中大量使用默认 default 和静态 static 方法,并且添加了默认方法,以便代码保持向下兼容。

    3. 功能接口和 Lambda 表达式

    什么是功能接口?

    具有一种抽象方法的接口,使用 @FunctionalInterface 注解进行表明。

    功能接口的主要优点之一是可以使用 lambda 表达式实例化它们。之前可以使用匿名类实例化一个接口,但是这样实现代码看起来很庞大。

    Runnable r = new Runnable(){
    			@Override
    			public void run() {
    				System.out.println(" Impetment Runnable");
    			}};
    			
    Runnable r1 = () -> {
    			System.out.println("Lambda Runnable");
    		};			
    

    因此,lambda 表达式可以轻松创建功能接口的匿名类的方法。由于,使用 lambda 表达式没有运行时的好处,一方面调试困难,另一方面维护者需要阅读整个 lambda 才能够理解其逻辑,因此建议谨慎使用它。

    4. Stream API 操作集合批量数据

    新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

    Stream 是对集合对象功能的增强,专注于对集合对象进行便利、高效的操作,或者进行大批量数据操作 ,就是一种流式处理,所有的数据像一条河流一样,Stream 中的函数就是针对这条数据河流进行相对应的处理,例如:filter 是对河流中符合对应的逻辑判断进行筛选。

    下面举例说明:

    public class StreamDemo {
    
        public static void main(String[] args) {
            List<Integer> listData = new ArrayList<>();
            for (int i = 0; i < 30; i++) {
                listData.add(i);
            }
    
            // 1. 获取流
            Stream<Integer> streamListData = listData.stream();
    
            // 2. 过滤流 获取集合中从1到10的数据集合
            Stream<Integer> limit1To10 = streamListData.filter(p -> p >= 1 && p <= 10);
    
            // 3. 打印信息
            limit1To10.forEach(p -> System.out.println("limit1To10 Nums ="+p));
    
        }
    }
    

    输出结果:

    limit1To10 Nums =1
    limit1To10 Nums =2
    limit1To10 Nums =3
    limit1To10 Nums =4
    limit1To10 Nums =5
    limit1To10 Nums =6
    limit1To10 Nums =7
    limit1To10 Nums =8
    limit1To10 Nums =9
    limit1To10 Nums =10
    

    Stream 还可以进行并行执行,下面介绍一下 并行执行的例子:

    package iterable.java8;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class StreamDemo {
    
        public static void main(String[] args) {
            List<Integer> listData = new ArrayList<>();
            for (int i = 0; i < 30; i++) {
                listData.add(i);
            }
    
            // 1. 获取流
            Stream<Integer> streamListData = listData.parallelStream();
    
    
            // 2. 过滤流 获取集合中从1到10的数据集合
            Stream<Integer> limit1To10 = streamListData.filter(p -> p >= 1 && p <= 10);
    
            // 3. 打印信息
            limit1To10.forEach(p -> System.out.println("limit1To10 Nums ="+p));
    
        }
    }
    

    注意这里 Stream<Integer> streamListData = listData.parallelStream();
    输出结果:

    limit1To10 Nums =9
    limit1To10 Nums =5
    limit1To10 Nums =3
    limit1To10 Nums =1
    limit1To10 Nums =4
    limit1To10 Nums =6
    limit1To10 Nums =10
    limit1To10 Nums =7
    limit1To10 Nums =2
    limit1To10 Nums =8
    

    通过对比上面两个例子的输出结果,不难发现使用了 parallelStream 的操作,没有按照顺序输出,因为是并行运作,因此在处理大量集合时并行处理将非常有帮助。

    5. Date Time API 加强对日期与时间的处理

    直到 JDK 1.7java.util.Datejava.util.Calendar 这两个类是处理日期和时间的主要类,但是这两个类的使用有以下问题:

    1. 线程不安全:java.util.Date 这个类是线程不安全;
    2. 处理麻烦:默认的开始日期从1900年,不支持国际化,不提供时区支持;
    3. 设计不合理:例如 java.utiljava.sql 包中都有日期类,类名却是一样的。

    JDK 1.8 中的新日期和时间API解决了的以上问题。
    java.time 包中包含API中的核心类, 此外,还有其他四个包,使用概率比较少

    • java.time.chrono:提供对不同的日历系统的访问
    • java.time.temporal:包括底层框架和扩展特性
    • java.time.format:提供用于打印和解析日期和时间的类
    • java.time.zone:提供对时区及其规则的支持
    6. 集合 API 改进
    • Iterator类 forEachRemaining(Consumer action) 在所有元素都已处理完毕或该动作引发异常之前,对每个剩余元素执行给定操作的默认方法,通过源码中 action.accept(next()); 其实是在对集合做数据处理的操作,这里插入了一个指定的动作 action 。
    • Collection类 removeIf(Predicate filter) 删除满足 filter 条件的集合中所有元素的方法。
    • Collection类 spliterator() 该方法返回 Spliterator 实例,该实例可用于顺序或并行遍历元素,是为了并行遍历元素而设计的一个迭代器。
    • Map类 replaceAll(),compute(),merge()方法。
    • HashMap类 解决Hash冲突的Hash算法的改进,Entry 数据链的算法改进。
    7. 并发 API 改进

    ConcurrentHashMap
    JDK1.8 中 针对 ConcurrentHashMap 做了一些改动,下面简单介绍一下 JDK1.7 ConcurrentHashMap 的实现原理:
    在这里插入图片描述

    将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对不属于同一个片段的节点可以并发操作。

    JDK1.8 做了如下改动:

    1. 取消 Segments 字段,直接采用 transient volatile HashEntry<K,V>[] table 保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,减少并发冲突的概率。
    2. 将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。

    Executors
    Executors 类新增 newWorkStealingPool() 线程池:使所有可用处理器作为目标,并行级别创建窃取线程池的方法。

    ”创建窃取“ 可以这么简单的理解:如果有三个线程 x , y , z ,如果 x 创建了3个任务, y 创建了 2 个任务, z 创建了 1 个任务, 假如,z 先 线程执行完任务后,它会主动的去 x 线程中窃取其他的任务进行执行(此时,x 线程尚未执行完成)。

    8. Optional 类,解决空指针问题

    Optional 类主要解决空指针异常 NullPointerException

    Optional 类的使用示例

    import java.util.Optional;
    
    public class OptionalDemo {
        public static void main(String[] args) {
    
            String str = "OptionalDemo";
            // 创建 Optional 
            Optional<String> opt = Optional.ofNullable(str);
    
            // 获取 Optional 中的值
            String optValue = opt.get();
        }
       
    }
    
    
    9. 新工具 新的编译工具

    Nashorn引擎jjs
    jjs 是一个基于标准 Nashorn 引擎的命令行工具,可以接受 js 源码并执行。

    编写示例,jjsDemo.js 文件,内容如下:

    function fun() { 
         return 1; 
    }; 
    
    print( fun() + 1 );
    

    在命令行中输入执行命令 jjs jjsDemo.js

    控制台输出结果是:

    2
    

    类依赖分析器jdeps

    jdeps 是一个命令行工具,它可以展示包层级和类层级的 Java 类依赖关系,以.class文件、目录或者Jar文件为输入,把依赖关系输出到控制台。
    例如直接在控制台输入:jdeps target
    此时输出在控制台上的内容将会是,目前这个工作目录直接的 Java 类依赖关系,如下:

    iterable.java8 (target)
          -> java.io                                            
          -> java.lang                                          
          -> java.lang.invoke                                   
          -> java.util                                          
          -> java.util.function                                 
          -> java.util.stream   
    

    结论

    本文讨论了 Java8 一些有趣的新功能和案例,Java 8 使得 Java 平台发展又前进了一大步。

    参考资料

    Java 8 新特性

    Java 8 Features with Examples (Java 8特性与示例

    New Features in Java 8(Java 8的新功能

    What’s New in JDK 8 (JDK 8的新增功能

    快过年了,今年最大的成就感,就是一直在朝着自己想要的方向前进。

    展开全文
  • java8新特性 lambda Stream map(函数式编程)

    万次阅读 多人点赞 2016-08-20 15:04:42
    java8新特性 lambda Stream map(函数式编程) 牛刀小试:使用Java8新特性获取股票数据https://blog.csdn.net/u014646662/article/details/82936131 Java8实战.pdf 下载:...
  • Java 8新特性

    千次阅读 2016-09-17 14:07:56
    Java Code Geeks上已经有大量的关于Java 8 的教程了,像玩转Java 8——lambda与并发,Java 8 Date Time API 教程: LocalDateTime和在Java 8中抽象类与接口的比较。我们也在其他地方引用了15个必读的Java 8教程。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 28,136
精华内容 11,254
关键字:

java8新特性

java 订阅