美文网首页
java(java8新特性)

java(java8新特性)

作者: luckee | 来源:发表于2019-01-17 22:18 被阅读0次

    java8新特性总结-1
    java8新特性总结-2
    体现在语言,类库,编译器,工具,运行时(JVM)五个方面

    lambda表达式

    lambda表达式只能作用于函数式接口(即只有一个抽象方法,不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的)

    @FunctionalInterface
    public interface FunctionalDefaultMethods {
        void method();
     
        default void defaultMethod() {            
        }        
    }
    

    最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,参数类型可以指定,也可以不指定,编译器会判断出;有多个参数的话,需要用括号括起来;语句块只有一条的话,不需要花括号,多条需要;如果有返回值,并且语句只有一条,那么return关键字也可以省略,编译器自动推断出(包括返回值类型和返回值)。lambda表达式简化了匿名类的使用,lambda表达式和匿名类使用时,如果引用了局部变量,则必须是final修饰的。因为回调函数不是立即执行,而是在将来执行,如果局部变量不声明为final的,本方法结束后就会被销毁掉,等到回调函数执行的时候,就不存在了;而如果是final修饰的话,final变量是放在方法区的,方法结束后,final变量并不会被销毁

    Arrays.asList( "a", "b", "d" ).forEach( (String e) -> {
        System.out.print( e );
        System.out.print( e );
    } );
    

    函数接口

    抽象方法只有一个(接口的方法默认都是抽象的,但可以有默认方法和静态方法)的接口,使用@FunctionalInterface注解,像Runnable,Callable,Comparator这些接口现在都有这个注解了。函数接口使得函数好像变成了一个对象,试想一下,一个函数接口类型的对象,不就是一个函数嘛

    image.png

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

    加入接口默认方法这种机制,使得能够兼容以前的实现类。在这之前,如果我们修改了接口(比如添加了新的方法),那么所有的实现类必须跟着修改(比如要实现接口中新添加的方法),但是加入了默认方法机制后,以前的实现类可以不用修改代码就能继承接口新添加的默认方法
    接口可以有默认方法和静态方法,默认方法使用default关键字声明。首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

    • 多个默认方法
      一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:
    public interface vehicle {
        default void print() {
            System.out.println("我是一辆车!");
        }
    }
    
    public interface fourWheeler {
        default void print() {
            System.out.println("我是一辆四轮车!");
        }
    }
    

    第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

    public class Car implements vehicle, fourWheeler {
    
        @Override
        public void print() {
            System.out.println("我是一辆四轮汽车!");
        }
    }
    

    第二种解决方案可以使用 super 来调用指定接口的默认方法:

    public class Car implements vehicle, fourWheeler {
        @Override
        public void print() {
            vehicle.super.print();
        }
    }
    
    • 静态方法
    public interface vehicle {
        default void print() {
            System.out.println("我是一辆车!");
        }
        // 静态方法
        static void blowHorn() {
            System.out.println("按喇叭!!!");
        }
    }
    

    方法引用

    • ClassName::new的形式引用类的构造函数,该构造函数没有参数
    • ClassName::static_method的形式引用静态方法,该静态方法有一个参数
    • ClassName::method的形式引用成员方法,该方法没有参数
    • instance::method的形式引用成员方法,该方法有一个参数

    重复注解

    允许在同一个地方多次使用同一个注解,该注解需要有@Repeatable

    拓宽的注解的使用场景

    现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上

    更好的类型推断

    Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁

    获取参数名称

    为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。

    package com.javacodegeeks.java8.parameter.names;
     
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
     
    public class ParameterNames {
        public static void main(String[] args) throws Exception {
            Method method = ParameterNames.class.getMethod( "main", String[].class );
            for( final Parameter parameter: method.getParameters() ) {
                System.out.println( "Parameter: " + parameter.getName() );
            }
        }
    }
    

    在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,输出为Parameter: arg0;如果带-parameters参数编译,输出为Parameter: args,如果使用maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <compilerArgument>-parameters</compilerArgument>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
    

    Optional

    Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。Optional仅仅是一个容器:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
    Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。

    Stream

    新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。Steam API极大得简化了集合操作(后面我们会看到不止是集合)Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

    Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。

    Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。

    元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
    Stream(流)是一个来自数据源的元素队列并支持聚合操作

    元素:是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。

    数据源 :流的来源。可以是集合,数组,I/O channel,产生器generator等。

    聚合操作: 类似SQL语句一样的操作,比如filter, map, reduce, find,match, sorted等。

    和以前的Collection操作不同,Stream操作还有两个基础的特征:

    Pipelining::中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)和短路( short-circuiting)。

    内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。

    • 在Java 8中,集合接口有两个方法来生成流:
      stream() −为集合创建串行流。
      parallelStream() − 为集合创建并行流。
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    }
    
    • forEach
      Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用forEach 输出了10个随机数:
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    
    • map
      map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    // 获取对应的平方数
    List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
    
    • filter
      filter 方法用于通过设置条件过滤出元素。以下代码片段使用filter 方法过滤出空字符串:
    List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    // 获取空字符串的数量
    int count = (int) strings.stream().filter(string -> string.isEmpty()).count();
    
    • limit
      limit 方法用于获取指定数量的流。以下代码片段使用 limit 方法打印出 10 条数据:
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    
    • sorted
      sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
    Random random = new Random();
    random.ints().limit(10).sorted().forEach(System.out::println);
    
    • 并行(parallel)程序
      parallelStream 是流并行处理程序的代替方法。以下实例我们使用parallelStream 来输出空字符串的数量:
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    // 获取空字符串的数量
    int count = (int) strings.parallelStream().filter(string -> string.isEmpty()).count();
    
    • Collectors
      Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors可用于返回列表或字符串
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    System.out.println("筛选列表: " + filtered);
    String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
    System.out.println("合并字符串: " + mergedString);
    
    • 统计
      另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
    IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("列表中最大的数 : " + stats.getMax());
    System.out.println("列表中最小的数 : " + stats.getMin());
    System.out.println("所有数之和 : " + stats.getSum());
    System.out.println("平均数 : " + stats.getAverage());
    

    Date/Time API

    新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。
    Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

    在旧版的Java 中,日期时间API 存在诸多问题,其中有:

    • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。

    • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

    • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

    Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

    • Local(本地) − 简化了日期时间的处理,没有时区的问题。

    • Zoned(时区) − 通过制定的时区处理日期时间。

    新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

    • Clock类
    • LocalDate和LocalTime类
    • LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息
    • ZoneDateTime类
    • Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同
    // Get duration between two dates
    final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
    final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
     
    final Duration duration = Duration.between( from, to );
    System.out.println( "Duration in days: " + duration.toDays() );
    System.out.println( "Duration in hours: " + duration.toHours() );
    

    Nashorn JavaScript引擎

    Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用。从JDK1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。与先前的Rhino实现相比,这带来了2到10倍的性能提升。

    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName( "JavaScript" );
     
    System.out.println( engine.getClass().getName() );
    System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
    

    输出为:

    jdk.nashorn.api.scripting.NashornScriptEngine
    Result: 2
    

    Base64编码

    对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,新的Base64API也支持URL和MINE的编码解码。
    (Base64.getUrlEncoder() / Base64.getUrlDecoder(),Base64.getMimeEncoder() / Base64.getMimeDecoder()),例子代码如下:

    package com.javacodegeeks.java8.base64;
     
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
     
    public class Base64s {
        public static void main(String[] args) {
            final String text = "Base64 finally in Java 8!";
     
            final String encoded = Base64
                .getEncoder()
                .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
            System.out.println( encoded );
     
            final String decoded = new String( 
                Base64.getDecoder().decode( encoded ),
                StandardCharsets.UTF_8 );
            System.out.println( decoded );
        }
    }
    

    并行数组

    Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:

    package com.javacodegeeks.java8.parallel.arrays;
     
    import java.util.Arrays;
    import java.util.concurrent.ThreadLocalRandom;
     
    public class ParallelArrays {
        public static void main( String[] args ) {
            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();
        }
    }
    

    上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:

    Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
    Sorted: 39 220 263 268 325 607 655 678 723 793
    

    并发

    基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。

    Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。

    java.util.concurrent.atomic包中也新增了不少工具类,列举如下:

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

    Nashorn引擎:jjs

    Java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具。jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:

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

    可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:2

    类依赖分析器:jdeps

    jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。
    我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar
    jdeps org.springframework.core-3.0.5.RELEASE.jar
    这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found"。

    org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
       org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
          -> java.io                                            
          -> java.lang                                          
          -> java.lang.annotation                               
          -> java.lang.ref                                      
          -> java.lang.reflect                                  
          -> java.util                                          
          -> java.util.concurrent                               
          -> org.apache.commons.logging                         not found
          -> org.springframework.asm                            not found
          -> org.springframework.asm.commons                    not found
       org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
          -> java.lang                                          
          -> java.lang.annotation                               
          -> java.lang.reflect                                  
          -> java.util
    

    JVM新特性

    使用MetaspaceJEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize-XX:MaxMetaspaceSize代替原来的-XX:PermSize-XX:MaxPermSize

    四大函数式接口

    • Function<T, R>
      T:入参类型,R:出参类型
      调用方法:R apply(T t);
      定义函数示例:Function<Integer, Integer> func = p -> p * 10; // 输出入参的10倍
      调用函数示例:func.apply(10); // 结果100

    • Consumer<T>
      T:入参类型;没有出参
      调用方法:void accept(T t);
      定义函数示例:Consumer<String> consumer= p -> System.out.println(p); // 因为没有出参,常用于打印、发送短信等消费动作
      调用函数示例:consumer.accept("18800008888");

    • Supplier<T>
      T:出参类型;没有入参
      调用方法:T get();
      定义函数示例:Supplier<Integer> supplier= () -> 100; // 常用于业务“有条件运行”时,符合条件再调用获取结果的应用场景;运行结果须提前定义,但不运行。
      调用函数示例:supplier.get();

    • Predicate<T>
      T:入参类型;出参类型是Boolean
      调用方法:boolean test(T t);
      定义函数示例:Predicate<Integer> predicate = p -> p % 2 == 0; // 判断是否、是不是偶数
      调用函数示例:predicate.test(100); // 运行结果true

    相关文章

      网友评论

          本文标题:java(java8新特性)

          本文链接:https://www.haomeiwen.com/subject/mgjwdqtx.html