美文网首页
Java 8新特性

Java 8新特性

作者: 周润发_223a | 来源:发表于2017-09-28 17:47 被阅读0次

    Lambda表达式与Functional接口

    Lambda表达式

    可以认为是一种特殊的匿名内部类
    lambda只能用于函数式接口。
    lambda语法:
    ([形参列表,不带数据类型])-> {
    //执行语句
    [return..;]
    }
    注意:
    1、如果形参列表是空的,只需要保留()即可
    2、如果没有返回值。只需要在{}写执行语句即可
    3、如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
    4、如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。
    5、如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的;号。
    6、形参列表的数据类型会自动推断,只需要参数名称。

    package com.Howard.test12;  
      
    public class TestLambda {  
         public static void main(String[] args) {  
               TestLanmdaInterface1 t1 = new TestLanmdaInterface1() {  
                    @Override  
                    public void test() {  
                         System.out.println("使用匿名内部类");  
      
                    }  
               };  
               //与上面的匿名内部类执行效果一样  
               //右边的类型会自动根据左边的类型进行判断  
               TestLanmdaInterface1 t2 = () -> {  
                    System.out.println("使用lanbda");  
               };  
               t1.test();  
               t2.test();  
      
               //如果执行语句只有一行,可以省略大括号  
               TestLanmdaInterface1 t3 = () -> System.out.println("省略执行语句大括号,使用lanbda");  
               t3.test();  
      
               TestLanmdaInterface2 t4 = (s) -> System.out.println("使用lanbda表达式,带1个参数,参数为:"+s);  
               t4.test("字符串参数1");  
      
               TestLanmdaInterface2 t5 = s -> System.out.println("使用lanbda表达式,只带1个参数,可省略参数的圆括号,参数为:"+s);  
               t5.test("字符串参数2");  
      
               TestLanmdaInterface3 t6 = (s,i) -> System.out.println("使用lanbda表达式,带两个参数,不可以省略圆括号,参数为:"+s+"  "+ i);  
               t6.test("字符串参数3",50);  
         }  
    }  
      
    @FunctionalInterface  
    interface TestLanmdaInterface1 {  
         //不带参数的抽象方法  
         void test();  
    }  
    @FunctionalInterface  
    interface TestLanmdaInterface2 {  
         //带参数的抽象方法  
         void test(String str);  
    }  
    @FunctionalInterface  
    interface TestLanmdaInterface3 {  
         //带多个参数的抽象方法  
         void test(String str,int num);  
    }  
    

    使用匿名内部类
    使用lanbda
    省略执行语句大括号,使用lanbda
    使用lammbda表达式,带1个参数,参数为:字符串参数为1
    使用lambda表达式,只带1个参数,可省略参数的圆括号,参数为:字符串参数2
    使用lambda表达式,带俩个参数,不可以省略圆括号,参数为:字符串参数3 50

    package com.Howard.test12;  
      
    public class CloseDoor {  
         public void doClose(Closeable c) {  
               System.out.println(c);  
               c.close();  
         }  
      
         public static void main(String[] args) {  
               CloseDoor cd = new CloseDoor();  
               cd.doClose(new Closeable() {  
                    @Override  
                    public void close() {  
                         System.out.println("使用匿名内部类实现");  
      
                    }  
               });  
      
               cd.doClose( () -> System.out.println("使用lambda表达式实现"));  
         }  
    }  
    @FunctionalInterface  
    interface Closeable {  
         void close();  
    }  
    

    com.Howard.test12.CloseDoor$1@15db9742
    使用匿名内部类实现
    com.Howard.test12.CloseDoor$$Lambda$1/91822158@4517d9a3
    使用Lambda表达式实现
    可以看出,lambda表达式和匿名内部类并不完全相同
    观察生成的class文件可以看出,lambda表达式并不会生成额外的.class文件,而匿名内部类会生成CloseDoor$1.class
    和匿名内部类一样,如果访问局部变量,要求局部变量必须是final,如果没有加final,会自动加上。

    public class TestLambdaReturn {  
         void re(LambdaReturn lr) {  
               int i = lr.test();  
               System.out.println("lambda表达式返回值是:"+i);  
         }  
      
         public static void main(String[] args) {  
               int i = 1000;  
               tlr.re( () -> i);  
                 
         }  
    }  
    interface LambdaReturn {  
         int test();  
    }  
                
    如果只是上面那样写,编译不会报错,但是如果改为:  
         public static void main(String[] args) {  
               int i = 1000;  
               tlr.re( () -> i); //报错  
               i = 10;  
         }  
    

    把i当作非final变量用,则lambda表达式那行会报错。

    方法引用

    引用实例方法:自动把调用方法的时候的参数,全部传给引用的方法
    <函数式接口> <变量名> = <实例> :: <实例方法名>
    //自动把实参传递给引用的实例方法
    <变量名>.<接口方法>([实参])
    引用类方法:自动把调用方法的时候的参数,全部传给引用的方法
    引用类的实例方法:定义、调用接口方法的时候需要多一个参数,并且参数的类型必须和引用实例方法的类型必须一致,
    把第一个参数作为引用的实例,后面的每个参数全部传递给引用的方法。
    interface <函数式接口> {
    <返回值> <方法名>(<类名><名称> [,其它参数...])
    }
    <变量名>.<方法名>(<类名的实例>[,其它参数])

    构造器的引用

    把方法的所有参数传递给引用的构造器,根据参数的类型来推断调用的构造器。
    参考下面代码

    package com.Howard.test12;  
      
    import java.io.PrintStream;  
    import java.util.Arrays;  
      
    /** 
     * 测试方法的引用 
     * @author Howard 
     * 2017年4月14日 
     */  
    public class TestMethodRef {  
         public static void main(String[] args) {  
               MethodRef r1 = (s) -> System.out.println(s);  
               r1.test("普通方式");  
      
               //使用方法的引用:实例方法的引用  
               //System.out是一个实例  out是PrintStream 类型,有println方法  
               MethodRef r2 = System.out::println;  
               r2.test("方法引用");  
      
               //MethodRef1 r3 =(a)-> Arrays.sort(a);  
               //引用类方法  
               MethodRef1 r3 = Arrays::sort;  
               int[] a = new int[]{4,12,23,1,3};  
               r3.test(a);  
               //将排序后的数组输出  
               r1.test(Arrays.toString(a));  
      
               //引用类的实例方法  
               MethodRef2 r4 = PrintStream::println;  
               //第二个之后的参数作为引用方法的参数  
               r4.test(System.out, "第二个参数");  
      
               //引用构造器  
               MethodRef3 r5 = String::new;  
               String test = r5.test(new char[]{'测','试','构','造','器','引','用'});  
               System.out.println(test);  
               //普通情况  
               MethodRef3 r6 = (c) -> {  
                    return new String(c);  
               };  
               String test2 = r6.test(new char[]{'测','试','构','造','器','引','用'});  
               System.out.println(test2);  
         }  
    }  
      
    interface MethodRef {  
         void test(String s);  
    }  
      
    interface MethodRef1 {  
         void test(int[] arr);  
    }  
      
    interface MethodRef2 {  
         void test(PrintStream out,String str);  
    }  
    //测试构造器引用  
    interface MethodRef3 {  
         String test(char[] chars);  
    }  
    

    普通方式
    方法引用
    [1,3,4,12,23]
    第二个参数
    测试构造器引用
    测试构造器引用

    函数式接口

    当接口里只有一个抽象方法的时候,就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口是函数式接口,即只能有一个抽象方法。
    例如:

    public interface Integerface1 {  
         void test();  
    }  
    

    上面的接口只有一个抽象方法,则默认是函数式接口。

    interface Integerface3 {  
         void test();  
         void test2();  
    }  
    

    该接口有两个抽象方法,不是函数式接口

    @FunctionalInterface  
    interface Integerface2 {  
          
    } 
    

    上面这样写编译会报错,因为@FunctionalInterface注解声明了该接口是函数式接口,必须且只能有一个抽象方法。
    如:

    @FunctionalInterface  
    interface Integerface2 {  
         void test();  
    } 
    

    Lambda表达式只能针对函数式接口使用。

    接口里的静态方法

    从java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是public。

    interface TestStaticMethod {  
         static void test1() {  
               System.out.println("接口里的静态方法!");  
         }  
    }  
    

    用接口类名调用静态方法:

    public class Test {  
         public static void main(String[] args) {  
               TestStaticMethod.test1();  
         }  
    } 
    

    接口里的静态方法!

    //函数式接口  
    @FunctionalInterface  
    interface TestStaticMethod {  
         //这是一个抽象方法  
         void test();  
         //静态方法,不是抽象方法  
         static void test1() {  
               System.out.println("接口里的静态方法!");  
         }  
    } 
    

    上面的代码编译器并不会报错,可以看到该接口仍然是函数式接口。

    接口的默认方法

    java8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。

    //非静态default方法  
    interface TestDefaultMethod{  
         default void test() {  
               System.out.println("这个是接口里的default方法test");  
         }  
         public default void test1() {  
               System.out.println("这个是接口里的default方法test1");  
         }  
         //编译报错  
    //   private default void test2() {  
    //         System.out.println("这个是接口里的default方法");  
    //   }  
    }
    

    由于不是静态方法,所以必须实例化才可以调用。

    public class Test {  
         public static void main(String[] args) {  
      
               //使用匿名内部类初始化实例  
               TestDefaultMethod tx = new TestDefaultMethod() {  
               };  
               tx.test();  
               tx.test1();  
         }  
    } 
    

    这个是接口里的default方法test
    这个是接口里的default方法test1
    默认方法可以被继承。但是要注意,如果继承了两个接口里面的默认方法一样的话,那么必须重写。
    如:

    interface A {  
         default void test() {  
               System.out.println("接口A的默认方法");  
         }  
    }  
    interface B {  
         default void test() {  
               System.out.println("接口B的默认方法");  
         }  
    }  
    interface C extends A,B {  
      
    }  
    

    这里接口c处会报错,因为编译器并不知道你到底继承的是A的默认方法还说B的默认方法。可以修改如下进行重写,用super明确调用哪个接口的方法:

    **java]** [view plain](http://blog.csdn.net/zymx14/article/details/70175746#) [copy](http://blog.csdn.net/zymx14/article/details/70175746#)
    
    interface C extends A,B {  
      
         @Override  
         default void test() {  
               A.super.test();  
         }  
      
    }  
    

    测试:

    public class Test {  
         public static void main(String[] args) {  
               C c = new C() {  
               };  
               c.test();  
         }  
    } 
    

    接口A的默认方法
    类继承两个有同样默认方法的接口也是一样,必须重写。
    下面的代码编译会报错

    class D implements A,B {  
         void test() {  
      
         }  
    }  
    

    因为A或B的test方法是默认方法,修饰符为public,重写该方法修饰符必须等于或者大于它,而public已经是最大的访问修饰符,所以这里修饰符必须是public

    class D implements A,B {  
         @Override  
         public void test() {  
               A.super.test();  
         }  
    } 
    
    public static void main(String[] args) {  
      
          D d = new D();  
          d.test();  
    } 
    

    接口A的默认方法
    注意:默认方法并不是抽象方法,所以下面这个接口仍是函数式接口.

    @FunctionalInterface  
    interface A {  
         default void test() {  
               System.out.println("接口A的默认方法");  
         }  
         void test1();  
    } 
    

    在接口里可以使用默认方法来实现父接口的抽象方法。如:

    interface C extends A,B {  
      
         @Override  
         default void test() {  
               A.super.test();  
         }  
         default void test1() {  
               System.out.println("在子接口实现父接口的抽象方法");  
         }  
      
    } 
    
    C c = new C() {  
     };  
    c.test1(); 
    

    在子接口实现父接口的抽象方法

    在实际使用匿名函数调用时可以重写:

    C c = new C() {  
         @Override  
         public void test1() {  
              System.out.println("调用时重写");  
         }  
    };  
    c.test1(); 
    

    调用时重写
    可以在子接口里重写父接口的默认方法,使其成为抽象方法。
    例如:

    interface E {  
         default void test() {  
               System.out.println("接口E的默认方法");  
         }  
    }  
    interface F extends E {  
        void test();  
    }  
    

    下面main方法里这样写不会报错

    E e = new E(){  
      
    };  
    e.test();
    

    但如果是这样:

    F f = new F(){  
      
    };  
    f.test();  
    

    则编译报错,要求你必须实现test()方法:

    图片.png

    可以改为

    public static void main(String[] args) {  
      
          F f = new F(){  
               @Override  
               public void test() {  
                    System.out.println("F接口实现");  
               }  
          };  
          f.test();  
    }  
    

    F接口实现

    重复注解

    自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
    在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:

    package com.javacodegeeks.java8.repeatable.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Repeatable;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    public class RepeatingAnnotations {
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        public @interface Filters {
            Filter[] value();
        }
    
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        @Repeatable( Filters.class )
        public @interface Filter {
            String value();
        };
    
        @Filter( "filter1" )
        @Filter( "filter2" )
        public interface Filterable {        
        }
    
        public static void main(String[] args) {
            for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
                System.out.println( filter.value() );
            }
        }
    }
    

    正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。
    另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:

    filter1
    filter2
    

    类型推断

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

    package com.javacodegeeks.java8.type.inference;
    
    public class Value< T > {
        public static< T > T defaultValue() { 
            return null; 
        }
    
        public T getOrDefault( T value, T defaultValue ) {
            return ( value != null ) ? value : defaultValue;
        }
    }
    

    下列代码是Value类型的应用

    package com.javacodegeeks.java8.type.inference;
    
    public class TypeInference {
        public static void main(String[] args) {
            final Value< String > value = new Value<>();
            value.getOrDefault( "22", Value.defaultValue() );
        }
    }
    

    参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用

    Value.<String>defaultValue()。
    

    拓宽注解的应用场景

    Java 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:

    package com.javacodegeeks.java8.annotations;
    
    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<>();        
        }
    }
    

    ElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。

    参数名称

    为了在运行时获得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() );
            }
        }
    }
    

    Java8中这个特性是默认关闭的,因此如果不带-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检查.
    接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

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

    如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。
    上述代码的输出结果如下:

    Full Name is set? false
    Full Name: [none]
    Hey Stranger!
    

    再看下另一个简单的例子:

    Optional< String > firstName = Optional.of( "Tom" );
    System.out.println( "First Name is set? " + firstName.isPresent() );        
    System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
    System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    System.out.println();
    

    这个例子的输出是:

    First Name is set? true
    First Name: Tom
    Hey Tom!
    

    Streams

    新增的[Stream API]java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。
    Steam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:

    public class Streams  {
        private enum Status {
            OPEN, CLOSED
        };
    
        private static final class Task {
            private final Status status;
            private final Integer points;
    
            Task( final Status status, final Integer points ) {
                this.status = status;
                this.points = points;
            }
    
            public Integer getPoints() {
                return points;
            }
    
            public Status getStatus() {
                return status;
            }
    
            @Override
            public String toString() {
                return String.format( "[%s, %d]", status, points );
            }
        }
    }
    

    Task类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:

    final Collection< Task > tasks = Arrays.asList(
        new Task( Status.OPEN, 5 ),
        new Task( Status.OPEN, 13 ),
        new Task( Status.CLOSED, 8 ) 
    );
    

    首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。

    // Calculate total points of all active tasks using sum()
    final long totalPointsOfOpenTasks = tasks
        .stream()
        .filter( task -> task.getStatus() == Status.OPEN )
        .mapToInt( Task::getPoints )
        .sum();
    
    System.out.println( "Total points: " + totalPointsOfOpenTasks );
    

    运行这个方法的控制台输出是:

    Total points: 18
    

    这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。
    在学习下一个例子之前,还需要记住一些steams的知识点。Steam之上的操作可分为中间操作和晚期操作。
    中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。
    晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。
    steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

    // Calculate total points of all tasks
    final double totalPoints = tasks
       .stream()
       .parallel()
       .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
       .reduce( 0, Integer::sum );
    
    System.out.println( "Total points (all tasks): " + totalPoints );
    

    这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:

    Total points(all tasks): 26.0
    

    对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:

    // Group tasks by their status
    final Map< Status, List< Task > > map = tasks
        .stream()
        .collect( Collectors.groupingBy( Task::getStatus ) );
    System.out.println( map );
    

    控制台的输出如下:

    {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
    

    最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:

    // Calculate the weight of each tasks (as percent of total points) 
    final Collection< String > result = tasks
        .stream()                                        // Stream< String >
        .mapToInt( Task::getPoints )                     // IntStream
        .asLongStream()                                  // LongStream
        .mapToDouble( points -> points / totalPoints )   // DoubleStream
        .boxed()                                         // Stream< Double >
        .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
        .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
        .collect( Collectors.toList() );                 // List< String > 
    
    System.out.println( result );
    

    控制台输出结果如下:

    [19%, 50%, 30%]
    

    最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:

    final Path path = new File( filename ).toPath();
    try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
        lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
    }
    

    Stream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。

    相关文章

      网友评论

          本文标题:Java 8新特性

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