Lambda表達(dá)式與Functional接口
Lambda表達(dá)式
可以認(rèn)為是一種特殊的匿名內(nèi)部類(lèi)
lambda只能用于函數(shù)式接口迂卢。
lambda語(yǔ)法:
([形參列表生音,不帶數(shù)據(jù)類(lèi)型])-> {
//執(zhí)行語(yǔ)句
[return..;]
}
注意:
1某宪、如果形參列表是空的,只需要保留()即可
2秧荆、如果沒(méi)有返回值该镣。只需要在{}寫(xiě)執(zhí)行語(yǔ)句即可
3、如果接口的抽象方法只有一個(gè)形參侮穿,()可以省略歌径,只需要參數(shù)的名稱(chēng)即可
4、如果執(zhí)行語(yǔ)句只有一行亲茅,可以省略{}回铛,但是如果有返回值時(shí),情況特殊克锣。
5茵肃、如果函數(shù)式接口的方法有返回值,必須給定返回值娶耍,如果執(zhí)行語(yǔ)句只有一句免姿,還可以簡(jiǎn)寫(xiě),即省去大括號(hào)和return以及最后的榕酒;號(hào)胚膊。
6、形參列表的數(shù)據(jù)類(lèi)型會(huì)自動(dòng)推斷想鹰,只需要參數(shù)名稱(chēng)紊婉。
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)部類(lèi)");
}
};
//與上面的匿名內(nèi)部類(lèi)執(zhí)行效果一樣
//右邊的類(lèi)型會(huì)自動(dòng)根據(jù)左邊的類(lèi)型進(jìn)行判斷
TestLanmdaInterface1 t2 = () -> {
System.out.println("使用lanbda");
};
t1.test();
t2.test();
//如果執(zhí)行語(yǔ)句只有一行,可以省略大括號(hào)
TestLanmdaInterface1 t3 = () -> System.out.println("省略執(zhí)行語(yǔ)句大括號(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)部類(lèi)
使用lanbda
省略執(zhí)行語(yǔ)句大括號(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)部類(lè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)部類(lèi)實(shí)現(xiàn)
com.Howard.test12.CloseDoor$$Lambda$1/91822158@4517d9a3
使用Lambda表達(dá)式實(shí)現(xiàn)
可以看出,lambda表達(dá)式和匿名內(nèi)部類(lèi)并不完全相同
觀察生成的class文件可以看出慨蛙,lambda表達(dá)式并不會(huì)生成額外的.class文件辽聊,而匿名內(nèi)部類(lèi)會(huì)生成CloseDoor$1.class
和匿名內(nèi)部類(lèi)一樣,如果訪問(wèn)局部變量期贫,要求局部變量必須是final跟匆,如果沒(méi)有加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();
}
如果只是上面那樣寫(xiě)玛臂,編譯不會(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í)參])
引用類(lèi)方法:自動(dòng)把調(diào)用方法的時(shí)候的參數(shù)敛瓷,全部傳給引用的方法
引用類(lèi)的實(shí)例方法:定義叁巨、調(diào)用接口方法的時(shí)候需要多一個(gè)參數(shù),并且參數(shù)的類(lèi)型必須和引用實(shí)例方法的類(lèi)型必須一致呐籽,
把第一個(gè)參數(shù)作為引用的實(shí)例锋勺,后面的每個(gè)參數(shù)全部傳遞給引用的方法。
interface <函數(shù)式接口> {
<返回值> <方法名>(<類(lèi)名><名稱(chēng)> [,其它參數(shù)...])
}
<變量名>.<方法名>(<類(lèi)名的實(shí)例>[,其它參數(shù)])
構(gòu)造器的引用
把方法的所有參數(shù)傳遞給引用的構(gòu)造器狡蝶,根據(jù)參數(shù)的類(lèi)型來(lái)推斷調(diào)用的構(gòu)造器庶橱。
參考下面代碼
package com.Howard.test12;
import java.io.PrintStream;
import java.util.Arrays;
/**
* 測(cè)試方法的引用
* @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 類(lèi)型,有println方法
MethodRef r2 = System.out::println;
r2.test("方法引用");
//MethodRef1 r3 =(a)-> Arrays.sort(a);
//引用類(lèi)方法
MethodRef1 r3 = Arrays::sort;
int[] a = new int[]{4,12,23,1,3};
r3.test(a);
//將排序后的數(shù)組輸出
r1.test(Arrays.toString(a));
//引用類(lèi)的實(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[]{'測(cè)','試','構(gòu)','造','器','引','用'});
System.out.println(test);
//普通情況
MethodRef3 r6 = (c) -> {
return new String(c);
};
String test2 = r6.test(new char[]{'測(cè)','試','構(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);
}
//測(cè)試構(gòu)造器引用
interface MethodRef3 {
String test(char[] chars);
}
普通方式
方法引用
[1,3,4,12,23]
第二個(gè)參數(shù)
測(cè)試構(gòu)造器引用
測(cè)試構(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 {
}
上面這樣寫(xiě)編譯會(huì)報(bào)錯(cuò)并淋,因?yàn)锧FunctionalInterface注解聲明了該接口是函數(shù)式接口,必須且只能有一個(gè)抽象方法珍昨。
如:
@FunctionalInterface
interface Integerface2 {
void test();
}
Lambda表達(dá)式只能針對(duì)函數(shù)式接口使用县耽。
接口里的靜態(tài)方法
從java8開(kāi)始接口里可以有靜態(tài)方式句喷,用static修飾,但是接口里的靜態(tài)方法的修飾符只能是public兔毙,且默認(rèn)是public唾琼。
interface TestStaticMethod {
static void test1() {
System.out.println("接口里的靜態(tài)方法!");
}
}
用接口類(lè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里,除了可以在接口里寫(xiě)靜態(tài)方法涌乳,還可以寫(xiě)非靜態(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)部類(lèi)初始化實(shí)例
TestDefaultMethod tx = new TestDefaultMethod() {
};
tx.test();
tx.test1();
}
}
這個(gè)是接口里的default方法test
這個(gè)是接口里的default方法test1
默認(rèn)方法可以被繼承征炼。但是要注意,如果繼承了兩個(gè)接口里面的默認(rèn)方法一樣的話躬贡,那么必須重寫(xiě)谆奥。
如:
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)方法還說(shuō)B的默認(rèn)方法拂玻∷嵝可以修改如下進(jìn)行重寫(xiě),用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();
}
}
測(cè)試:
public class Test {
public static void main(String[] args) {
C c = new C() {
};
c.test();
}
}
接口A的默認(rèn)方法
類(lèi)繼承兩個(gè)有同樣默認(rèn)方法的接口也是一樣檐蚜,必須重寫(xiě)魄懂。
下面的代碼編譯會(huì)報(bào)錯(cuò)
class D implements A,B {
void test() {
}
}
因?yàn)锳或B的test方法是默認(rèn)方法,修飾符為public闯第,重寫(xiě)該方法修飾符必須等于或者大于它市栗,而public已經(jīng)是最大的訪問(wèn)修飾符,所以這里修飾符必須是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)方法來(lái)實(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í)可以重寫(xiě):
C c = new C() {
@Override
public void test1() {
System.out.println("調(diào)用時(shí)重寫(xiě)");
}
};
c.test1();
調(diào)用時(shí)重寫(xiě)
可以在子接口里重寫(xiě)父接口的默認(rèn)方法填帽,使其成為抽象方法。
例如:
interface E {
default void test() {
System.out.println("接口E的默認(rèn)方法");
}
}
interface F extends E {
void test();
}
下面main方法里這樣寫(xiě)不會(huì)報(bào)錯(cuò)
E e = new E(){
};
e.test();
但如果是這樣:
F f = new F(){
};
f.test();
則編譯報(bào)錯(cuò)诲泌,要求你必須實(shí)現(xiàn)test()方法:
可以改為
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中引入注解以來(lái)盲赊,這個(gè)特性開(kāi)始變得非常流行,并在各個(gè)框架和項(xiàng)目中被廣泛使用敷扫。不過(guò)哀蘑,注解有一個(gè)很大的限制是:在同一個(gè)地方不能多次使用同一個(gè)注解诚卸。Java 8打破了這個(gè)限制,引入了重復(fù)注解的概念绘迁,允許在同一個(gè)地方多次使用同一個(gè)注解合溺。
在Java 8中使用@Repeatable注解定義重復(fù)注解,實(shí)際上缀台,這并不是語(yǔ)言層面的改進(jìn)棠赛,而是編譯器做的一個(gè)trick,底層的技術(shù)仍然相同膛腐【υ迹可以利用下面的代碼說(shuō)明:
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() );
}
}
}
正如我們所見(jiàn),這里的Filter類(lèi)使用@Repeatable(Filters.class)注解修飾哲身,而Filters是存放Filter注解的容器辩涝,編譯器盡量對(duì)開(kāi)發(fā)者屏蔽這些細(xì)節(jié)。這樣勘天,F(xiàn)ilterable接口可以用兩個(gè)Filter注解注釋?zhuān)ㄟ@里并沒(méi)有提到任何關(guān)于Filters的信息)怔揩。
另外,反射API提供了一個(gè)新的方法:getAnnotationsByType()脯丝,可以返回某個(gè)類(lèi)型的重復(fù)注解商膊,例如Filterable.class.getAnnoation(Filters.class)將返回兩個(gè)Filter實(shí)例,輸出到控制臺(tái)的內(nèi)容如下所示:
filter1
filter2
類(lèi)型推斷
Java 8編譯器在類(lèi)型推斷方面有很大的提升宠进,在很多場(chǎng)景下編譯器可以推導(dǎo)出某個(gè)參數(shù)的數(shù)據(jù)類(lèi)型晕拆,從而使得代碼更為簡(jiǎn)潔。例子代碼如下:
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類(lèi)型的應(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()的類(lèi)型由編譯器推導(dǎo)得出砰苍,不需要顯式指明潦匈。在Java 7中這段代碼會(huì)有編譯錯(cuò)誤,除非使用
Value.<String>defaultValue()赚导。
拓寬注解的應(yīng)用場(chǎng)景
Java 8拓寬了注解的應(yīng)用場(chǎng)景〔缢酰現(xiàn)在,注解幾乎可以使用在任何元素上:局部變量吼旧、接口類(lèi)型凰锡、超類(lèi)和接口實(shí)現(xiàn)類(lèi),甚至可以用在函數(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è)注解掂为,用于描述注解的使用場(chǎng)景。Java 語(yǔ)言也做了對(duì)應(yīng)的改變员串,以識(shí)別這些新增的注解勇哗。
參數(shù)名稱(chēng)
為了在運(yùn)行時(shí)獲得Java程序中方法的參數(shù)名稱(chēng),老一輩的Java程序員必須使用不同方法寸齐,例如Paranamer liberary欲诺。Java 8終于將這個(gè)特性規(guī)范化抄谐,在語(yǔ)言層面(使用反射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)用中最常見(jiàn)的bug就是空值異常塞颁。在Java 8之前浦箱,Google Guava引入了Optionals類(lèi)來(lái)解決NullPointerException,從而避免源碼被各種null檢查污染祠锣,以便開(kāi)發(fā)者寫(xiě)出更加整潔的代碼酷窥。Java 8也將Optional加入了官方庫(kù)。
Optional僅僅是一個(gè)容易:存放T類(lèi)型的值或者null锤岸。它提供了一些有用的接口來(lái)避免顯式的null檢查.
接下來(lái)看一點(diǎn)使用Optional的例子:可能為空的值或者某個(gè)類(lèi)型的值:
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()方法類(lèi)似放接,但是在持有null的時(shí)候返回傳入的默認(rèn)值刺啦。
上述代碼的輸出結(jié)果如下:
Full Name is set? false
Full Name: [none]
Hey Stranger!
再看下另一個(gè)簡(jiǎn)單的例子:
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庫(kù)中。這是目前為止最大的一次對(duì)Java庫(kù)的完善纠脾,以便開(kāi)發(fā)者能夠?qū)懗龈佑行耆场⒏雍?jiǎn)潔和緊湊的代碼。
Steam API極大得簡(jiǎn)化了集合操作(后面我們會(huì)看到不止是集合)苟蹈,首先看下這個(gè)叫Task的類(lèi):
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類(lèi)有一個(gè)分?jǐn)?shù)(或偽復(fù)雜度)的概念糊渊,另外還有兩種狀態(tài):OPEN或者CLOSED。現(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è)問(wèn)題:在這個(gè)task集合中一共有多少個(gè)OPEN狀態(tài)的點(diǎn)慧脱?在Java 8之前渺绒,要解決這個(gè)問(wèn)題,則需要使用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)值得說(shuō)氮采。首先殷绍,tasks集合被轉(zhuǎn)換成steam表示;其次鹊漠,在steam上的filter操作會(huì)過(guò)濾掉所有CLOSED的task主到;第三殖侵,mapToInt操作基于每個(gè)task實(shí)例的Task::getPoints方法將task流轉(zhuǎn)換成Integer集合;最后镰烧,通過(guò)sum方法計(jì)算總和拢军,得出最后的結(jié)果。
在學(xué)習(xí)下一個(gè)例子之前怔鳖,還需要記住一些steams的知識(shí)點(diǎn)茉唉。Steam之上的操作可分為中間操作和晚期操作。
中間操作會(huì)返回一個(gè)新的steam——執(zhí)行一個(gè)中間操作(例如filter)并不會(huì)執(zhí)行實(shí)際的過(guò)濾操作结执,而是創(chuàng)建一個(gè)新的steam度陆,并將原steam中符合條件的元素放入新創(chuàng)建的steam。
晚期操作(例如forEach或者sum)献幔,會(huì)遍歷steam并得出結(jié)果或者附帶結(jié)果懂傀;在執(zhí)行晚期操作之后,steam處理線已經(jīng)處理完畢蜡感,就不能使用了蹬蚁。在幾乎所有情況下,晚期操作都是立刻對(duì)steam進(jìn)行遍歷郑兴。
steam的另一個(gè)價(jià)值是創(chuàng)造性地支持并行處理(parallel processing)犀斋。對(duì)于上述的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
對(duì)于一個(gè)集合却舀,經(jīng)常需要根據(jù)某些條件對(duì)其中的元素分組虫几。利用steam提供的API可以很快完成這類(lèi)任務(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集合的例子問(wèn)題是:如何計(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%]
最后辆脸,正如之前所說(shuō),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對(duì)軟件開(kāi)發(fā)的現(xiàn)代范式的響應(yīng)穗椅。