Java基础Lambda表达式就是这么简单!
Lambda表达式是Java8中新增的特性,lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。在以前定义的方法中,只能将基本类型或者引用类型的变量作为方法参数,在Java8以后可以将一个代码片段作为方法参数。Lambda表达式入门
在集合中Java为开发者提供了遍历集合的简洁方式,如下例所示:packagecn。bytecollege;importjava。util。List;importjava。util。ArrayList;publicclassLambdaDemo{publicstaticvoidmain(String〔〕args){ListStringlistnewArrayList();list。add(张三);list。add(李四);list。add(王五);list。forEach(eSystem。out。println(e));}}
在上面的示例中,调用了list对象的foreach方法,从程序可以看出,传入foreach的并不是一个变量,而是一段代码,这就是Lambda表达式。从上面的语法可以看出,Lambda表达式的主要作用就是代替匿名内部类的烦琐语法。
Lambda由3部分组成:形参列表:形参列表允许省略形参的数据类型,如果形参列表中有且只有1个参数,可以省略形参列表的括号箭头函数:必须有横线和大于号组成代码块。如果代码块只包含一条语句,Lambda表达式允许省略代码块的花括号。
下面,通过示例来学习Lambda的写法:表达式只有1个参数(a){System。out。print(a);}表达式可以简写为a{System。out。print(a);}如果代码块中只有1条语句,可以省略大括号aSystem。out。print(a)函数式接口
Lambda表达式的目标类型必须是函数式接口,所谓函数式接口代表只包含一个抽象方法的接口,函数式接口可以包含多个默认方法、类方法,但是只能声明一个抽象方法。
如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下可采用Lambda表达式来创建对象。
注意:Java8专门为函数式接口提供了FunctionalInterface注解,该注解通常放在接口定义前,该注解对程序功能没有任何作用,它的作用是用于告诉编译器执行更严格的检查,检查该接口必须是函数式接口,否则编译器出错。
Lambda表达式的结果就是被作为对象,程序中晚期可以使用Lambda表达式进行赋值,例如在多线程Thread类的构造器中可以传入Runnable接口的子类对象。查看Runnable接口发现,该接口也被声明为一个函数式接口:FunctionalInterfacepublicinterfaceRunnable{publicabstractvoidrun();}
所以,就可以使用Lambda表达式来创建线程:packagecn。bytecollege;publicclassThreadLambdaDemo{publicstaticvoidmain(String〔〕args){ThreadthreadnewThread((){for(inti0;i10;i){System。out。println(i);}});thread。start();}}
Lambda表达式实现的是匿名方法因此它只能实现特定函数式接口中的唯一方法。这意味着Lambda表达式有如下两个限制。Lambda表达式的目标类型必须是明确的函数式接口。Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象。
下面定义一个函数式接口深入学习Lambda表达式packagecn。bytecollege;函数式接口只能有一个抽象方法,并且要使用FunctionalInterface声明FunctionalInterfacepublicinterfaceConsumer{intadd(inta,intb);}
定义一个方法,方法参数是Consumer接口:packagecn。bytecollege;publicclassMyTest{publicstaticinttest(Consumerconsumer){inta5;intb4;returnconsumer。add(a,b);}publicstaticvoidmain(String〔〕args){intktest((a,b){returnab;});System。out。println(k);}}
在上例中定义了一个函数式接口,在测试类的test方法传入了接口并调用了Consumer接口的add方法,需要注意的是,此时add方法并没有方法实现,在main方法中调用了test,并将一段代码(即add方法的实现)也就是lambda表达式当做参数传入了test方法。换句话说在上例中使用了lambda表达替代了烦琐的匿名内部类。对比下面的代码就可以看出Lambda表达式的独到之处。packagecn。bytecollege;publicclassMyTest{publicstaticinttest(Consumerconsumer){inta5;intb4;returnconsumer。add(a,b);}publicstaticvoidmain(String〔〕args){intktest(newConsumer(){Overridepublicintadd(inta,intb){returnab;}});System。out。println(k);}}
从前面的程序可以看出Lambda表达式的使用离不开函数式接口,通常函数式接口中有且只能有1个抽象方法,这样使用Lambda表达式时也就明确了是哪个抽象方法的实现,如果接口中出现了多个抽象方法,那么就不能在接口上使用FunctionInterface注解,会编译出错。因此,Java8在java。util。function包中预定义了大量函数式接口,通常情况下这些接口完全可以满足开发需要:XxxFunction这类接口中通常包含一个apply()抽象方法,该方法对参数进行处理、转换(apply()方法的处理逻辑由Lambda表达式来实现),然后返回一个新的值。该函数式接口通常用于对指定数据进行转换处理。XxxConsumer这类接口中通常包含一个accept()抽象方法,该方法与XxxFunction接口中的apply()方法基本相似,也负责对参数进行处理,只是该方法不会返回处理结果。XxxxPredicate这类接口中通常包含一个test()抽象方法,该方法通常用来对参数进行某种判断test()方法的判断逻辑由Lambda表达式来实现),然后返回一个boolean值。该接口通常用于判断参数是否满足特定条件,经常用于进行筛滤数据。XxxSupplier这类接口中通常包含一个getAsXxx()抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法(getAsXxx()方法的逻辑算法由Lambda表达式来实现)返回一个数据。综上所述,不难发现Lambda表达式的本质很简单,就是使用简洁的语法来创建函数式接口的实例这种语法避免了匿名内部类的烦琐。
下面在程序中示范上述接口的使用:packagecn。bytecollege。lambda;importjava。util。function。Function;数据转换authorMR。WpublicclassCastUtil{定义方法将Object类型转换为String类型paramfunctionparamoreturnpublicstaticStringcastToString(FunctionObject,Stringfunction,Integera){returnfunction。apply(a);}}
在上面的CastUtil类中定义了castToString,在该方法中第一个参数是一个Java8预定义的函数式接口,在方法内调用了Function接口的apply()方法,作用是将任意类型转换成String。但是此时这个方法并没有方法的实现,需要在调用此方法时传入方法的实现。packagecn。bytecollege。lambda;importjava。util。function。Function;publicclassTest{publicstaticvoidmain(String〔〕args){Integera10010;使用Lambda表达式,此时castToString方法的第一个参数就是Function函数式接口apply()的实现StringsCastUtil。castToString((o){returnString。valueOf(o);},a);System。out。println(s);}}
在测试类中,调用了CastUtil的castToString()方法,并传入了Lambda表达式,以此Lambda表达式作为apply()方法的实现,在表达式中使用了String。valueOf()方法将对象转换成String类型。方法引用与构造器引用
前面已经介绍过,如果Lambda表达式的代码块只有一条代码,程序就可以省略Lambda表达式中代码块的花括号。不仅如此,如果Lambda表达式的代码块只有一条代码,还可以在代码块中使用方法引用和构造器引用。方法引用和构造器引用可以让Lambda表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。
Lambda表达式支持如下表所示的几种引用方式。引用类方法
下面的示例将演示类方法的引用,首先定义一个函数式接口,接口中定义抽象方法castToString(),该方法的作用是将一个对象转换成String对象。packagecn。bytecollege。lambda;FunctionalInterfacepublicinterfaceFunctionT,R{RcastToString(Tt);}
在String的学习中可以知道,String类有提供了类方法valueOf(Objecto),该方法可以将任意对象转换成String类型,因此可以使用该方法作为Lambda表达式的实现代码:packagecn。bytecollege。lambda;publicclassRefTest{publicstaticvoidmain(String〔〕args){FunctionObject,Stringfunctiona{returnString。valueOf(a);};System。out。println(function。castToString(张三));}}
在上面的代码中,创建了Lambda表达式作为了Function接口中castToString()方法的实现。在Lambda表达式中调用了String。valueOf()方法来进行对象到字符串的转换,在代码第8行调用了function接口的castToString()方法,实际上调用了就是代码第5行创建的Lambda表达式。
上面的Lambda表达式的代码块只有一行调用类方法的代码,因此可以使用如下方法引用进行替换。代码如下:packagecn。bytecollege。lambda;publicclassRefTest{publicstaticvoidmain(String〔〕args){FunctionObject,StringfunctionString::valueOf;System。out。println(function。castToString(张三));}}
对于上面的类方法的引用,也就是调用了String类的valueOf()方法来实现Function函数式接口中唯一抽象方法。当调用castToString()方法时,调用参数将会传给String类的valueOf()类方法。引用对象的实例方法
下面演示第二种方法引用,引用对象的实例方法,首先使用Lambda表达式创建一个Function接口的子类对象:FunctionObject,Stringfunctionoo。toString();
上面的Lambda表达式只有一条语句,因此省略了该代码的花括号。
接下来程序调用function对象的castToString()方法:packagecn。bytecollege。lambda;publicclassRefTest{publicstaticvoidmain(String〔〕args){FunctionObject,Stringfunctionoo。toString();System。out。println(function。castToString(100));}}
上面的程序调用了function对象的castToString()方法时,由于function对象是Lambda表达式创建,castToString()方法的执行体就是Lambda表达式的代码部分,因此上面的程序输出了100。
上面的Lambda表达式代码只有一行,且调用了对象的o的toString()实例方法。因此代码可以进行如下替换:packagecn。bytecollege。lambda;publicclassRefTest{publicstaticvoidmain(String〔〕args){FunctionObject,StringfunctionObject::toString;System。out。println(function。castToString(100));}}
上面的Lambda表达式的代码只有一条语句,因此省略了代码块的花括号;而且由于表达式实现的castToString方法需要返回值,因此Lambda表达会将这行代码的值作为返回值。此时就可以使用方法引用进行替换,直接引用Object的toString()方法作为Lambda表达式的代码块。其中Function接口的castToString方法有个参数,当执行Lambda表达式代码块时,会自动调用传入参数的toString()方法。引用构造器
下面的实例将演示如何引用构造器,首先定义函数式接口:packagecn。bytecollege。lambda;FunctionalInterfacepublicinterfaceMyInterface{StringBuilderget(Strings);}
该函数式接口包含了一个get()抽象方法,该方法的作用是使用String对象生成一个StringBuilder对象,接着使用Lambda表达式创建一个MyInterface的对象:packagecn。bytecollege。lambda;publicclassRefTest3{publicstaticvoidmain(String〔〕args){MyInterfacemyInterface(s)newStringBuilder(s);StringBuildersbmyInterface。get(张三);}}
上面的代码调用了myInterface对象的get()方法时,由于该对象是Lambda表达式创建的,因此get()方法执行体就是Lambda表达式的代码块部分,即执行体就是执行newStringBuilder(a)语句,并将这条语句的值作为方法的返回值。因此上面代码中Lambda表达式的代码可以进行如下替换:packagecn。bytecollege。lambda;publicclassRefTest3{publicstaticvoidmain(String〔〕args){MyInterfacemyInterfaceStringBuilder::new;StringBuildersbmyInterface。get(张三);}}
对于上面的构造器引用,也就是调用StringBuilder类的构造方法来实现MyInteface函数式接口中唯一的抽象方法,当调用MyInterface接口的get()方法时,调用参数会传给StringBuilder构造器,从上面的程序中可以看出,调用myInterface对象的get()方法时,实际只传入了一个String类型的参数,这个String类型的参数会被传给StringBuilder的构造器。Lambda表达式和匿名内部类的联系和区别
从前面介绍可以看出,Lambda表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用,Lambda表达式与匿名内部类存在如下相同点。Lambda表达式与匿名内部类一样,都可以直接访问effectivelyfinal的局部变量,以及外部类的成员变量(包括实例变量和类变量)。Lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
首先创建函数式接口:packagecn。bytecollege。ano;FunctionalInterfacepublicinterfaceDisplay{intadd(inta,intb);defaultvoidprint(){System。out。println(Hello!);}}packagecn。bytecollege。ano;publicclassLambdaTest{privateintage18;privatestaticStringnameByte科技;publicvoidtest(){StringbookJava编程思想;Displaydisplay(a,b){访问外部类的实例变量System。out。println(age);访问外部类的类变量System。out。println(name);访问局部变量System。out。println(book);returnab;};调用display对象从接口继承的默认方法display。print();bookJava核心技术卷;System。out。println(display。add(1,2));}}
创建测试类:packagecn。bytecollege。ano;publicclassTest{publicstaticvoidmain(String〔〕args){LambdaTesttestnewLambdaTest();test。test();}}
上面的程序使用Lambda表达式创建了一个Display接口的对象,Lambda表达式分别访问了外部类的实例变量,类变量从这些来看Lambda表达式的代码块和匿名内部类的方法体是相同的。
和匿名内部类相似,由于Lambda表达式访问了了book局部变量,因此该局部变量相当于有一个隐式的final修饰,不允许对book局部变量重新赋值。
当程序使用Lambda表达式创建了Display的对象之后,该对象不仅可调用接口中唯一的抽象方法,也可调用接口中的默认方法。
Lambda表达式与匿名内部类主要存在如下区别:匿名内部类可以为任意接口创建实例不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但Lambda表达式只能为函数式接口创建实例。匿名内部类可以为抽象类甚至普通类创建实例;但Lambda表达式只能为函数式接口创建实例。匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。