Java8 新特性快速入门

作者: Yodes | 来源:发表于2018-03-09 14:28 被阅读18次

    点击图片访问我的个人博客
      自2014年3月18日发布以来,四年的时间里各大厂商已经逐渐采用Java8重构工程软件,招聘需求里也已将其作为Java开发工程师的必备技能。掌握Java8新特性成了软件开发人员不得不重视的能力。本人结合相关教程及书籍学习了Java8新特性,特此分享。

    Lambda表达式(闭包)

      Lambda(λ,希腊数字中的第十一个数字,由于λ演算式的存在,它也代表闭包)表达式是JAVA 8中最令人激动的新特性,它使得Java编程中出现了函数式编程的概念,在其他高级语言中如Python(解释型、动态数据类型、面向对象),常用Lambda表达式创建匿名函数1。Lambda表达式允许我们将函数当成一个参数看待,可以将其传递给一个方法或者直接将表达式所在代码块作为数据处理,这一设计在JAVA 8之前只能采用匿名内部类2的方式实现,这损耗了大量的编程时长及读码效率。

    Example 1 遍历数组

    Java 7

    Integer[] arr = new Integer[]{2, 9, -2, 3};
    for(int i : arr){
        System.out.println(i);
    }
    

    Java 8

    Arrays.asList(2, 9, -2, 3).foreach( (Integer e) -> {
            System.out.println(e);
        } );
    

      可以看到最简单的Lambda表达式可以由数值列表,(变量代表名),->以及{行为代码}组成。但实际上,()包括变量e的类型名都是可以省略的,省略的变量类型名由编译器自行推理,深知{}也是可以省略的,所以最短代码应该是:

    Arrays.adList(2, 9, -2, 3).foreach( e -> System.out.println(e) );
    

      Lambda表达式可以引用类成员变量或全局变量,但JVM会自动将其隐形转换成final类型,理由与匿名内部类的参数引用时必须为final一致3

    String separator = ",";
    Arrays.adList(2, 9, -2, 3).foreach( e -> System.out.print(e) + separator ); // 变量separator将被隐式转换为final类型。
    

      为了使Java中原有的功能能够与Lambda表达式结合使用,官方规定函数接口(除下文即将介绍的默认函数及静态函数外,只有一个函数的接口)能够隐式转换成Lambda表达式,java.lang.Runnable和java.concurrent.Callable是函数接口的最佳例子。此外,为了解决函数接口定义与Lambda表达式的冲突,官方提供了一个特殊的注解@FuntionalInterface用以表示函数接口,这意味着你以后定义上述函数接口将采用此注解标识,在Jdk中所有相关的函数也已经加上此注解。如:

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    接口新增默认方法和静态方法

      众所周知,接口相关知识点是面试中老生常谈的话题。在以往接口被定义为抽象方法的集合,接口中的方法会被隐式指定为public abstract,而变量会被隐式指定为public static final,其他修饰符会导致报错。而现在,Java中接口的定义将被修改,接口中除public抽象函数外新增了默认方法和静态方法。

    public interface Defaulable {
        default void hello(){
            System.out.println("这是接口的默认方法");
        }
    
        static void create(){
            System.out.println("这是接口的静态方法");
        } 
    }
            
    public class DefauleImp implements Defaulable{
    //    @Override
    //    public void hello() {
    //        System.out.println("Hello world");
    //    }
    
        public static void main(String[] args) {
            new DefauleImp().hello();
            Defaulable.create();
        }
    }
    
    

      默认方法可被继承或重写,而静态方法可与类的静态方法一样直接通过<em>接口名.方法名</em>调用。

    方法引用

      方法引用的最大用途是简写Lambda表达式。

    方法引用 Lambda表达式
    String::valueOf x -> String.valueOf(x)
    Object::toString x -> x.toString()
    x::toString () -> x.toString()
    ArrayList::new () -> new ArrayList<>()

    拓宽注解的使用范围

      犹记得在旧版的Java教程中没有注解的出现,当学习Struts等第三方流行框架时出现注解使我极其不适。而现在注解已经成为JAVA世界一种独特且无可替代的定义方式。JAVA8中,注解几乎可以用在任何元素之上:类、接口、元素、方法,甚至是异常。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.ArrayList;
    import java.util.Collection;
    
    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以前,需要在运行时得到参数的名称是一件比较困难的事情,程序员们虽然提供了诸如Paranamer liberary等方法,但用起来总归不算顺畅,而现在Java8从字节码层面(使用新的javac编译器以及-parameters参数)和语言层面(Parameter.getName()和反射API)提供了这一支持。

    public static void main(String[] args) {
        for (Method m : ${ClassName}.class.getMethods()) {
            System.out.println("----------------------------------------");
            System.out.println("   method: " + m.getName());
            System.out.println("   return: " + m.getReturnType().getName());
            for (Parameter p : m.getParameters()) {
                System.out.println("parameter: " + p.getType().getName() + ", " + p.getName());
            }
        }
    }
    

      但是在JAVA8中这个功能是默认关闭的,如果需要打开需要加上参数-parameters进行编译,如果你使用maven作为构建工具,也可以直接在编译插件中加入此参数,如下:

    <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中经常会出现NullException,为了检验空值异常,程序员经常需要添加许多与业务逻辑无关的检测代码,这既破坏了代码美感,也耗费了宝贵的开发时间,因此Java8中仿照谷歌开源库Guava使用了Optional类,此类提供了有效的接口用于null检查,如下:

    Optional< String > fullName = Optional.ofNullable( "null" );
    System.out.println( "Full Name is set? " + fullName.isPresent() );
    System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
    System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    

    Stream

      Stream也是Java中非常重要的一个特性,在Java文档中这样定义Stream:

    A sequence of elements supporting sequential and parallel aggregate operations.

    翻译一下,即:

    1. 元素的集合,这也使得Stream类似于Iterator;
    2. 可以顺序或是并行的对原数据进行操作。

      显而易见,Stream的设计源于分治法,学过并行计算框架MapReduce或Fork/Join的同学更容易理解。

    由于Stream所属知识篇幅较大,有兴趣的同学可以关注本人的【翻译】Java8 Stream API 教程

    附录

    1. 匿名函数:指程序中无需定义标识符(即函数名)的函数类型或子程序(代码段)。
    2. 匿名内部类:无类名的类,它必须且仅能继承一个父类或实现一个接口。
    3. 为什么匿名内部类参数引用必须为final?

    相关文章

      网友评论

        本文标题:Java8 新特性快速入门

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