Java 8新特性

Lambda表達(dá)式與Functional接口

Lambda表達(dá)式

可以認(rèn)為是一種特殊的匿名內(nèi)部類
lambda只能用于函數(shù)式接口。
lambda語法:
([形參列表,不帶數(shù)據(jù)類型])-> {
//執(zhí)行語句
[return..;]
}
注意:
1、如果形參列表是空的,只需要保留()即可
2、如果沒有返回值。只需要在{}寫執(zhí)行語句即可
3、如果接口的抽象方法只有一個(gè)形參,()可以省略,只需要參數(shù)的名稱即可
4、如果執(zhí)行語句只有一行,可以省略{},但是如果有返回值時(shí),情況特殊。
5、如果函數(shù)式接口的方法有返回值,必須給定返回值,如果執(zhí)行語句只有一句,還可以簡寫,即省去大括號(hào)和return以及最后的;號(hào)。
6、形參列表的數(shù)據(jù)類型會(huì)自動(dòng)推斷,只需要參數(shù)名稱。

package com.Howard.test12;  
  
public class TestLambda {  
     public static void main(String[] args) {  
           TestLanmdaInterface1 t1 = new TestLanmdaInterface1() {  
                @Override  
                public void test() {  
                     System.out.println("使用匿名內(nèi)部類");  
  
                }  
           };  
           //與上面的匿名內(nèi)部類執(zhí)行效果一樣  
           //右邊的類型會(huì)自動(dòng)根據(jù)左邊的類型進(jìn)行判斷  
           TestLanmdaInterface1 t2 = () -> {  
                System.out.println("使用lanbda");  
           };  
           t1.test();  
           t2.test();  
  
           //如果執(zhí)行語句只有一行,可以省略大括號(hào)  
           TestLanmdaInterface1 t3 = () -> System.out.println("省略執(zhí)行語句大括號(hào),使用lanbda");  
           t3.test();  
  
           TestLanmdaInterface2 t4 = (s) -> System.out.println("使用lanbda表達(dá)式,帶1個(gè)參數(shù),參數(shù)為:"+s);  
           t4.test("字符串參數(shù)1");  
  
           TestLanmdaInterface2 t5 = s -> System.out.println("使用lanbda表達(dá)式,只帶1個(gè)參數(shù),可省略參數(shù)的圓括號(hào),參數(shù)為:"+s);  
           t5.test("字符串參數(shù)2");  
  
           TestLanmdaInterface3 t6 = (s,i) -> System.out.println("使用lanbda表達(dá)式,帶兩個(gè)參數(shù),不可以省略圓括號(hào),參數(shù)為:"+s+"  "+ i);  
           t6.test("字符串參數(shù)3",50);  
     }  
}  
  
@FunctionalInterface  
interface TestLanmdaInterface1 {  
     //不帶參數(shù)的抽象方法  
     void test();  
}  
@FunctionalInterface  
interface TestLanmdaInterface2 {  
     //帶參數(shù)的抽象方法  
     void test(String str);  
}  
@FunctionalInterface  
interface TestLanmdaInterface3 {  
     //帶多個(gè)參數(shù)的抽象方法  
     void test(String str,int num);  
}  

使用匿名內(nèi)部類
使用lanbda
省略執(zhí)行語句大括號(hào),使用lanbda
使用lammbda表達(dá)式,帶1個(gè)參數(shù),參數(shù)為:字符串參數(shù)為1
使用lambda表達(dá)式,只帶1個(gè)參數(shù),可省略參數(shù)的圓括號(hào),參數(shù)為:字符串參數(shù)2
使用lambda表達(dá)式,帶倆個(gè)參數(shù),不可以省略圓括號(hào),參數(shù)為:字符串參數(shù)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("使用匿名內(nèi)部類實(shí)現(xiàn)");  
  
                }  
           });  
  
           cd.doClose( () -> System.out.println("使用lambda表達(dá)式實(shí)現(xiàn)"));  
     }  
}  
@FunctionalInterface  
interface Closeable {  
     void close();  
}  

com.Howard.test12.CloseDoor$1@15db9742
使用匿名內(nèi)部類實(shí)現(xiàn)
com.Howard.test12.CloseDoor$$Lambda$1/91822158@4517d9a3
使用Lambda表達(dá)式實(shí)現(xiàn)
可以看出,lambda表達(dá)式和匿名內(nèi)部類并不完全相同
觀察生成的class文件可以看出,lambda表達(dá)式并不會(huì)生成額外的.class文件,而匿名內(nèi)部類會(huì)生成CloseDoor$1.class
和匿名內(nèi)部類一樣,如果訪問局部變量,要求局部變量必須是final,如果沒有加final,會(huì)自動(dòng)加上。

public class TestLambdaReturn {  
     void re(LambdaReturn lr) {  
           int i = lr.test();  
           System.out.println("lambda表達(dá)式返回值是:"+i);  
     }  
  
     public static void main(String[] args) {  
           int i = 1000;  
           tlr.re( () -> i);  
             
     }  
}  
interface LambdaReturn {  
     int test();  
}  
            
如果只是上面那樣寫,編譯不會(huì)報(bào)錯(cuò),但是如果改為:  
     public static void main(String[] args) {  
           int i = 1000;  
           tlr.re( () -> i); //報(bào)錯(cuò)  
           i = 10;  
     }  

把i當(dāng)作非final變量用,則lambda表達(dá)式那行會(huì)報(bào)錯(cuò)。

方法引用

引用實(shí)例方法:自動(dòng)把調(diào)用方法的時(shí)候的參數(shù),全部傳給引用的方法
<函數(shù)式接口> <變量名> = <實(shí)例> :: <實(shí)例方法名>
//自動(dòng)把實(shí)參傳遞給引用的實(shí)例方法
<變量名>.<接口方法>([實(shí)參])
引用類方法:自動(dòng)把調(diào)用方法的時(shí)候的參數(shù),全部傳給引用的方法
引用類的實(shí)例方法:定義、調(diào)用接口方法的時(shí)候需要多一個(gè)參數(shù),并且參數(shù)的類型必須和引用實(shí)例方法的類型必須一致,
把第一個(gè)參數(shù)作為引用的實(shí)例,后面的每個(gè)參數(shù)全部傳遞給引用的方法。
interface <函數(shù)式接口> {
<返回值> <方法名>(<類名><名稱> [,其它參數(shù)...])
}
<變量名>.<方法名>(<類名的實(shí)例>[,其它參數(shù)])

構(gòu)造器的引用

把方法的所有參數(shù)傳遞給引用的構(gòu)造器,根據(jù)參數(shù)的類型來推斷調(diào)用的構(gòu)造器。
參考下面代碼

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("普通方式");  
  
           //使用方法的引用:實(shí)例方法的引用  
           //System.out是一個(gè)實(shí)例  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);  
           //將排序后的數(shù)組輸出  
           r1.test(Arrays.toString(a));  
  
           //引用類的實(shí)例方法  
           MethodRef2 r4 = PrintStream::println;  
           //第二個(gè)之后的參數(shù)作為引用方法的參數(shù)  
           r4.test(System.out, "第二個(gè)參數(shù)");  
  
           //引用構(gòu)造器  
           MethodRef3 r5 = String::new;  
           String test = r5.test(new char[]{'測','試','構(gòu)','造','器','引','用'});  
           System.out.println(test);  
           //普通情況  
           MethodRef3 r6 = (c) -> {  
                return new String(c);  
           };  
           String test2 = r6.test(new char[]{'測','試','構(gòu)','造','器','引','用'});  
           System.out.println(test2);  
     }  
}  
  
interface MethodRef {  
     void test(String s);  
}  
  
interface MethodRef1 {  
     void test(int[] arr);  
}  
  
interface MethodRef2 {  
     void test(PrintStream out,String str);  
}  
//測試構(gòu)造器引用  
interface MethodRef3 {  
     String test(char[] chars);  
}  

普通方式
方法引用
[1,3,4,12,23]
第二個(gè)參數(shù)
測試構(gòu)造器引用
測試構(gòu)造器引用

函數(shù)式接口

當(dāng)接口里只有一個(gè)抽象方法的時(shí)候,就是函數(shù)式接口,可以使用注解(@FunctionalInterface)強(qiáng)制限定接口是函數(shù)式接口,即只能有一個(gè)抽象方法。
例如:

public interface Integerface1 {  
     void test();  
}  

上面的接口只有一個(gè)抽象方法,則默認(rèn)是函數(shù)式接口。

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

該接口有兩個(gè)抽象方法,不是函數(shù)式接口

@FunctionalInterface  
interface Integerface2 {  
      
} 

上面這樣寫編譯會(huì)報(bào)錯(cuò),因?yàn)锧FunctionalInterface注解聲明了該接口是函數(shù)式接口,必須且只能有一個(gè)抽象方法。
如:

@FunctionalInterface  
interface Integerface2 {  
     void test();  
} 

Lambda表達(dá)式只能針對函數(shù)式接口使用。

接口里的靜態(tài)方法

從java8開始接口里可以有靜態(tài)方式,用static修飾,但是接口里的靜態(tài)方法的修飾符只能是public,且默認(rèn)是public。

interface TestStaticMethod {  
     static void test1() {  
           System.out.println("接口里的靜態(tài)方法!");  
     }  
}  

用接口類名調(diào)用靜態(tài)方法:

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

接口里的靜態(tài)方法!

//函數(shù)式接口  
@FunctionalInterface  
interface TestStaticMethod {  
     //這是一個(gè)抽象方法  
     void test();  
     //靜態(tài)方法,不是抽象方法  
     static void test1() {  
           System.out.println("接口里的靜態(tài)方法!");  
     }  
} 

上面的代碼編譯器并不會(huì)報(bào)錯(cuò),可以看到該接口仍然是函數(shù)式接口。

接口的默認(rèn)方法

java8里,除了可以在接口里寫靜態(tài)方法,還可以寫非靜態(tài)方法,但是必須用default修飾,且只能是public,默認(rèn)也是public。

//非靜態(tài)default方法  
interface TestDefaultMethod{  
     default void test() {  
           System.out.println("這個(gè)是接口里的default方法test");  
     }  
     public default void test1() {  
           System.out.println("這個(gè)是接口里的default方法test1");  
     }  
     //編譯報(bào)錯(cuò)  
//   private default void test2() {  
//         System.out.println("這個(gè)是接口里的default方法");  
//   }  
}

由于不是靜態(tài)方法,所以必須實(shí)例化才可以調(diào)用。

public class Test {  
     public static void main(String[] args) {  
  
           //使用匿名內(nèi)部類初始化實(shí)例  
           TestDefaultMethod tx = new TestDefaultMethod() {  
           };  
           tx.test();  
           tx.test1();  
     }  
} 

這個(gè)是接口里的default方法test
這個(gè)是接口里的default方法test1
默認(rèn)方法可以被繼承。但是要注意,如果繼承了兩個(gè)接口里面的默認(rèn)方法一樣的話,那么必須重寫。
如:

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

這里接口c處會(huì)報(bào)錯(cuò),因?yàn)榫幾g器并不知道你到底繼承的是A的默認(rèn)方法還說B的默認(rèn)方法??梢孕薷娜缦逻M(jìn)行重寫,用super明確調(diào)用哪個(gè)接口的方法:

**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的默認(rèn)方法
類繼承兩個(gè)有同樣默認(rèn)方法的接口也是一樣,必須重寫。
下面的代碼編譯會(huì)報(bào)錯(cuò)

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

因?yàn)锳或B的test方法是默認(rèn)方法,修飾符為public,重寫該方法修飾符必須等于或者大于它,而public已經(jīng)是最大的訪問修飾符,所以這里修飾符必須是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的默認(rèn)方法
注意:默認(rèn)方法并不是抽象方法,所以下面這個(gè)接口仍是函數(shù)式接口.

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

在接口里可以使用默認(rèn)方法來實(shí)現(xiàn)父接口的抽象方法。如:

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

在子接口實(shí)現(xiàn)父接口的抽象方法

在實(shí)際使用匿名函數(shù)調(diào)用時(shí)可以重寫:

C c = new C() {  
     @Override  
     public void test1() {  
          System.out.println("調(diào)用時(shí)重寫");  
     }  
};  
c.test1(); 

調(diào)用時(shí)重寫
可以在子接口里重寫父接口的默認(rèn)方法,使其成為抽象方法。
例如:

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

下面main方法里這樣寫不會(huì)報(bào)錯(cuò)

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

但如果是這樣:

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

則編譯報(bào)錯(cuò),要求你必須實(shí)現(xiàn)test()方法:

圖片.png

可以改為

public static void main(String[] args) {  
  
      F f = new F(){  
           @Override  
           public void test() {  
                System.out.println("F接口實(shí)現(xiàn)");  
           }  
      };  
      f.test();  
}  

F接口實(shí)現(xiàn)

重復(fù)注解

自從Java 5中引入注解以來,這個(gè)特性開始變得非常流行,并在各個(gè)框架和項(xiàng)目中被廣泛使用。不過,注解有一個(gè)很大的限制是:在同一個(gè)地方不能多次使用同一個(gè)注解。Java 8打破了這個(gè)限制,引入了重復(fù)注解的概念,允許在同一個(gè)地方多次使用同一個(gè)注解。
在Java 8中使用@Repeatable注解定義重復(fù)注解,實(shí)際上,這并不是語言層面的改進(jìn),而是編譯器做的一個(gè)trick,底層的技術(shù)仍然相同??梢岳孟旅娴拇a說明:

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注解的容器,編譯器盡量對開發(fā)者屏蔽這些細(xì)節(jié)。這樣,F(xiàn)ilterable接口可以用兩個(gè)Filter注解注釋(這里并沒有提到任何關(guān)于Filters的信息)。
另外,反射API提供了一個(gè)新的方法:getAnnotationsByType(),可以返回某個(gè)類型的重復(fù)注解,例如Filterable.class.getAnnoation(Filters.class)將返回兩個(gè)Filter實(shí)例,輸出到控制臺(tái)的內(nèi)容如下所示:

filter1
filter2

類型推斷

Java 8編譯器在類型推斷方面有很大的提升,在很多場景下編譯器可以推導(dǎo)出某個(gè)參數(shù)的數(shù)據(jù)類型,從而使得代碼更為簡潔。例子代碼如下:

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類型的應(yīng)用

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

參數(shù)Value.defaultValue()的類型由編譯器推導(dǎo)得出,不需要顯式指明。在Java 7中這段代碼會(huì)有編譯錯(cuò)誤,除非使用

Value.<String>defaultValue()。

拓寬注解的應(yīng)用場景

Java 8拓寬了注解的應(yīng)用場景?,F(xiàn)在,注解幾乎可以使用在任何元素上:局部變量、接口類型、超類和接口實(shí)現(xiàn)類,甚至可以用在函數(shù)的異常定義上。下面是一些例子:

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新增的兩個(gè)注解,用于描述注解的使用場景。Java 語言也做了對應(yīng)的改變,以識(shí)別這些新增的注解。

參數(shù)名稱

為了在運(yùn)行時(shí)獲得Java程序中方法的參數(shù)名稱,老一輩的Java程序員必須使用不同方法,例如Paranamer liberary。Java 8終于將這個(gè)特性規(guī)范化,在語言層面(使用反射API和Parameter.getName()方法)和字節(jié)碼層面(使用新的javac編譯器以及-parameters參數(shù)

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中這個(gè)特性是默認(rèn)關(guān)閉的,因此如果不帶-parameters參數(shù)編譯上述代碼并運(yùn)行,則會(huì)輸出如下結(jié)果:

Parameter: arg0

如果帶-parameters參數(shù),則會(huì)輸出如下結(jié)果(正確的結(jié)果):

Parameter: args

如果你使用Maven進(jìn)行項(xiàng)目管理,則可以在maven-compiler-plugin編譯器的配置項(xiàng)中配置-parameters參數(shù):

<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應(yīng)用中最常見的bug就是空值異常。在Java 8之前,Google Guava引入了Optionals類來解決NullPointerException,從而避免源碼被各種null檢查污染,以便開發(fā)者寫出更加整潔的代碼。Java 8也將Optional加入了官方庫。
Optional僅僅是一個(gè)容易:存放T類型的值或者null。它提供了一些有用的接口來避免顯式的null檢查.
接下來看一點(diǎn)使用Optional的例子:可能為空的值或者某個(gè)類型的值:

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實(shí)例持有一個(gè)非空值,則isPresent()方法返回true,否則返回false;orElseGet()方法,Optional實(shí)例持有null,則可以接受一個(gè)lambda表達(dá)式生成的默認(rèn)值;map()方法可以將現(xiàn)有的Opetional實(shí)例的值轉(zhuǎn)換成新的值;orElse()方法與orElseGet()方法類似,但是在持有null的時(shí)候返回傳入的默認(rèn)值。
上述代碼的輸出結(jié)果如下:

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

再看下另一個(gè)簡單的例子:

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

這個(gè)例子的輸出是:

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

Streams

新增的[Stream API]java.util.stream)將生成環(huán)境的函數(shù)式編程引入了Java庫中。這是目前為止最大的一次對Java庫的完善,以便開發(fā)者能夠?qū)懗龈佑行А⒏雍啙嵑途o湊的代碼。
Steam API極大得簡化了集合操作(后面我們會(huì)看到不止是集合),首先看下這個(gè)叫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類有一個(gè)分?jǐn)?shù)(或偽復(fù)雜度)的概念,另外還有兩種狀態(tài):OPEN或者CLOSED?,F(xiàn)在假設(shè)有一個(gè)task集合:

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

首先看一個(gè)問題:在這個(gè)task集合中一共有多少個(gè)OPEN狀態(tài)的點(diǎn)?在Java 8之前,要解決這個(gè)問題,則需要使用foreach循環(huán)遍歷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 );

運(yùn)行這個(gè)方法的控制臺(tái)輸出是:

Total points: 18

這里有很多知識(shí)點(diǎn)值得說。首先,tasks集合被轉(zhuǎn)換成steam表示;其次,在steam上的filter操作會(huì)過濾掉所有CLOSED的task;第三,mapToInt操作基于每個(gè)task實(shí)例的Task::getPoints方法將task流轉(zhuǎn)換成Integer集合;最后,通過sum方法計(jì)算總和,得出最后的結(jié)果。
在學(xué)習(xí)下一個(gè)例子之前,還需要記住一些steams的知識(shí)點(diǎn)。Steam之上的操作可分為中間操作和晚期操作。
中間操作會(huì)返回一個(gè)新的steam——執(zhí)行一個(gè)中間操作(例如filter)并不會(huì)執(zhí)行實(shí)際的過濾操作,而是創(chuàng)建一個(gè)新的steam,并將原steam中符合條件的元素放入新創(chuàng)建的steam。
晚期操作(例如forEach或者sum),會(huì)遍歷steam并得出結(jié)果或者附帶結(jié)果;在執(zhí)行晚期操作之后,steam處理線已經(jīng)處理完畢,就不能使用了。在幾乎所有情況下,晚期操作都是立刻對steam進(jìn)行遍歷。
steam的另一個(gè)價(jià)值是創(chuàng)造性地支持并行處理(parallel processing)。對于上述的tasks集合,我們可以用下面的代碼計(jì)算所有任務(wù)的點(diǎn)數(shù)之和:

// 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方法計(jì)算最終的結(jié)果??刂婆_(tái)輸出如下:

Total points(all tasks): 26.0

對于一個(gè)集合,經(jīng)常需要根據(jù)某些條件對其中的元素分組。利用steam提供的API可以很快完成這類任務(wù),代碼如下:

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

控制臺(tái)的輸出如下:

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

最后一個(gè)關(guān)于tasks集合的例子問題是:如何計(jì)算集合中每個(gè)任務(wù)的點(diǎn)數(shù)在集合中所占的比重,具體處理的代碼如下:

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

控制臺(tái)輸出結(jié)果如下:

[19%, 50%, 30%]

最后,正如之前所說,Steam API不僅可以作用于Java集合,傳統(tǒng)的IO操作(從文件或者網(wǎng)絡(luò)一行一行得讀取數(shù)據(jù))可以受益于steam處理,這里有一個(gè)小例子:

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 返回一個(gè)等價(jià)的有額外句柄的Stream,當(dāng)Stream的close()方法被調(diào)用的時(shí)候這個(gè)句柄會(huì)被執(zhí)行。Stream API、Lambda表達(dá)式還有接口默認(rèn)方法和靜態(tài)方法支持的方法引用,是Java 8對軟件開發(fā)的現(xiàn)代范式的響應(yīng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容