[TOC] Lambda表达式支持将代码块做为方法的参数,Lambda表达式容许使用更加简洁的代码来建立一个只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。 #1、Lambda表达式入门——为了不匿名内部类的繁琐 咱们前面介绍了Command表达式的例子: 定义一个处理数组元素的接口java
package one; public interface Command { //接口里定义的Process方法用于封装“处理行为” void process(int element); }
定义一个处理数组的类android
package two; import one.Command; public class ProcessArray { public void process(int[] target,Command cmd) { for(var t:target) { cmd.process(t); } } }
import one.Command; import two.ProcessArray; class CommandTest1 { public static void main(String[] args) { var pa=new ProcessArray(); int[] a={1,5,9,7}; pa.process(a,new Command(){ public void process(int element) { System.out.println("数组元素的平方:"+element*element); } }); } } ---------- 运行Java捕获输出窗 ---------- 数组元素的平方:1 数组元素的平方:25 数组元素的平方:81 数组元素的平方:49 输出完成 (耗时 0 秒) - 正常终止
import one.Command; import two.ProcessArray; class CommandTest2 { public static void main(String[] args) { var pa=new ProcessArray(); int[] a={1,5,9,7}; pa.process(a,(int element)-> System.out.println("数组元素的平方:"+element*element)); } }
这段代码代码与建立匿名内部类时实现的process(int element)方法彻底相同,只是不须要new Xxx(){}的繁琐形式,不须要指出重写方法的名字,也不须要指出重写方法的返回值类型,只须要给出重写方法括号以及括号里的形参列表便可。 ##三、Lambda语句的组成 Lambda表达式主要用于代替匿名内部类的繁琐语法。它由三部分组成: 一、形参列表。形参列表容许是省略类型。若是形参列表只有一个参数,甚至连形参列表的圆括号也能够省略。 二、箭头(->) 三、代码块。若是代码块只有一条语句容许省略代码块的花括号;若是只有一条return语句,甚至能够省略return关键字。Lambda表达式须要返回值,而他的代码块仅有一条省略了return 语句,Lambda表达式会自动返回这条语句的值。 Lambda表达式的集中简化形式:ios
interface Eatable { void taste();//public abstract } interface Flyable { void fly(String weather); } interface Addable { int add(int a,int b); } public class LambdaQs { //调用该方法须要Eatable对象 public void eat(Eatable e) { System.out.println(e); e.taste(); } //调用该方法须要Flyable对象 public void drive(Flyable f) { System.out.println("我正在驾驶:"+f); f.fly("[碧空如洗的晴天]"); } //调用该方法须要Addable对象 public void test(Addable add) { System.out.println("3加5的和为:"+add.add(3,5)); } public static void main(String[] args) { var lq=new LambdaQs(); //Lambda语句只有一条语句,能够省略花括号 lq.eat(()->System.out.println("苹果味道不错!")); //Lambda表达式形参列表只有一个形参,能够省略圆括号 lq.drive(weather->{ System.out.println("今每天气是"+weather); System.out.println("直升机平稳飞行");}); //Lambda只有一条语句时,能够省略花括号 //代码块只有一条语句,即便该表达式须要返回值,也能够省略return关键字 lq.test((a,b)->{return (a+b);}); lq.test((a,b)->a+b); } } ---------- 运行Java捕获输出窗 ---------- LambdaQs$$Lambda$1/0x0000000801201040@72ea2f77 苹果味道不错! 我正在驾驶:LambdaQs$$Lambda$2/0x0000000801201840@eed1f14 今每天气是[碧空如洗的晴天] 直升机平稳飞行 3加5的和为:8 3加5的和为:8 输出完成 (耗时 0 秒) - 正常终止
lq.eat()使用不带形参列表的匿名方法,因为该Lambda表达式只有一条代码,所以能够省略花括号; lq.drive()的Lambda表达式的形参列表只有一个形参,所以省略了形参列表的圆括号; lq.test()的Lambda表达式的代码块只有一行语句,这行语句的返回值做为该代码块的返回值。编程
Lambda表达式的类型,也成为“目标类型(target type)”,Lambda表达式的目标类型必须是“函数式接口(functional interface)”。<font color=red>函数式接口表明只包含一个一个抽象方法的接口。函数式接口能够包含多个默认方法、类方法,但只能声明一个抽象方法。</font>数组
若是采用匿名内部类语法来建立函数式接口,且只须要实现一个抽象方法,在这种状况下,便可采用Lambda表达式来建立对象,该表示建立出来的对象的目标类型就是函数式接口。 注: Java 8专门为函数式接口提供了@FunctionalInterface注解,该注解用于方法在接口定义前面,该注解对程序功能没有任何影响,它用于告诉编译器执行更严格的检查——检查该接口必须是函数式接口,不然编译就会出错。 下面程序使用匿名内部类:函数式编程
/*@FunctionalInterface *A 不是函数接口 * 在 接口 A 中找到多个非覆盖抽象方法 */ interface A { public void test1(); public void test2(); default void test3()//接口中的默认方法 { System.out.println("接口A中的默认方法"); } } public class 适用匿名内部类 { public void test(A a) { System.out.println("接口A含有两个抽象方法和一个默认方法,此时适合用匿名内部类"); a.test1(); a.test2(); a.test3(); } public static void main(String[] args) { var p=new 适用匿名内部类(); p.test(new A() { public void test1() { System.out.println("接口中的抽象方法1"); } public void test2() { System.out.println("接口中的抽象方法2"); } }); } } 接口A含有两个抽象方法和一个默认方法,此时适合用匿名内部类 接口中的抽象方法1 接口中的抽象方法2 接口A中的默认方法
下面定义的接口B只有一个抽象方法,是函数式接口,此时适合用Lambda表达式:函数
@FunctionalInterface interface B { void test1(String msg);//抽象方法,默认public abstract default void test2()//接口中的默认方法 { System.out.println("接口A中的默认方法"); } } public class LambdaFor { public void test(B b) { System.out.println("接口A含有1个抽象方法和一个默认方法,是函数式接口"); b.test1("函数式接口A中的抽象方法"); b.test2(); } public static void main(String[] args) { var p=new LambdaFor(); p.test((msg)-> System.out.println(msg)); } } ---------- 运行Java捕获输出窗 ---------- 接口A含有1个抽象方法和一个默认方法,是函数式接口 函数式接口A中的抽象方法 接口A中的默认方法 输出完成 (耗时 0 秒) - 正常终止
用于Lambda表达式的结果就是被当成对象,所以程序中彻底可使用Lambda表达式进行赋值。咱们知道接口不能建立实例,接口中只能定义常量,所以接口不存在构造器和初始化块。接口不能建立实例,可是经过Lambda表达式咱们能够建立一个“目标类型”并把它赋值给函数式接口的对象。 例如:spa
@FunctionalInterface interface Runnable { void printNum(); } public class RunnableTest { public static void main(String[] args) { //Runnable接口中只包含一个无参数的构造器方法 //Lambda表达式表明的匿名方法实现了Runnable接口中惟一的无参数方法 //所以下面的方法建立了一个Runnable的对象 Runnable r=()->{ for(int i=0;i<10;i++) System.out.print(" "+i); }; r.printNum(); } } ---------- 运行Java捕获输出窗 ---------- 0 1 2 3 4 5 6 7 8 9 输出完成 (耗时 0 秒) - 正常终止
<font color=red>Lambda表达式实现的匿名方法——所以它只能实现特定函数式接口中惟一方法。这意味着Lambda表达式有两个限制: 一、Lambda表达式的目标类型必须是明确的函数式接口。 二、Lambda表达式只能为函数式接口建立对象。Lambda表达式只能实现一个方法,所以他只能为只有一个抽象方法的接口(函数式接口)建立对象。</font> 关于第一点限制举例:code
@FunctionalInterface interface A { void test(); } class LambdaLimit1 { public static void main(String[] args) { //Object a=()->{System.out.println("This is a test!");}; //上面代码将报错: 不兼容的类型: Object 不是函数接口 //Lambda表达式的目标类型必须是明确的函数式接口 A a=()->{System.out.println("This is a test!");}; a.test();//This is a test! } }
从错误信息能够看出,Lambda表达式的目标类型必须是明确的函数式接口。上述表达式将Lambda表达式赋给Object变量,编译器只能肯定该表达式的类型为Object,而Object并非函数式接口。 为了保证Lambda表达式的目标类型是一个明确的函数式接口,常见有三种方式: 一、将Lambda表达式赋值给函数式接口的变量;orm
//参考上面的完整程序 A a=()->{System.out.println("This is a test!");};
二、将Lambda表达式做为函数接口类型的参数传给某个方法。
interface A { void test(String msg); } public class ATest { public static void med(A a) { System.out.println("主类的非静态方法"); a.test("我是传奇"); } public static void main(String[] args) { ATest.med((msg)->System.out.println(msg)); } } ---------- 运行Java捕获输出窗 ---------- 主类的非静态方法 我是传奇 输出完成 (耗时 0 秒) - 正常终止
三、使用函数式接口类型对Lambda表达式进行强制转换。
Object a=(A)()->{System.out.println("This is a test!");};
对与var声明变量,程序可使用Lambda表达式进行赋值。但因为var表明须要由编译器推断的类型,所以使用Lambda表达式对var表达式定义的变量进行赋值时,必须指明Lambda表达式的目标类型。 例如:
var a=(A)()->{System.out.println("This is a test!");};
若是程序须要对Lambda表达式的形参列表添加注解,此时就不能省略Lambda表达式的形参类型——由于注解只能放在形参类型以前。在Java 11以前,程序必须严格声明Lambda表达式中的每一个形参类型,但实际上编译器彻底能够推断出lambda表达式中每一个形参的类型。
例如:下面程序定义了一个Predator接口,该接口中的prey方法的形参使用了@NotNull注解修饰:
@interface NotNull{} interface Predator { void prey(@NotNull String animal); }
接下来程序打算使用Lambda表达式来实现一个Predator对象。若是Lambda表达式不须要对animal形参使用@NotNull注解,则彻底能够省略animal形参注解;但若是但愿为animal形参注解,则必须为形参声明类型,此时可直接使用var来声明形参类型。
@interface NotNull{} interface Predator { void prey(@NotNull String animal); } public class PredatorTest { public static void main(String[] args) { //使用var声明lambda表达式的形参类型 //这样便可为Lambda表达式的形参添加注解 Predator p=(@NotNull var animal)->System.out.println("老鹰在抓"+animal); p.prey("小鸡"); } } //老鹰在抓小鸡
#4、方法引用和构造器引用 Lambda表达式的方法引用和构造器引用都须要两个英文冒号::。Lambda表达式支持以下几种引用方式:
种类 | 示例 | 说明 | 对应的Lambda表达式 |
---|---|---|---|
引用类方法 | 类名::类方法名 | 函数式接口中被实现的方法的参数所有传给该类方法做为参数 | (a,b...)->类名.类方法(a,b...) |
引用特定对象的实例方法 | 特定对象::示例方法名 | 函数式接口中被实现的方法的参数所有传给该实例方法做为参数 | (a,b...)->特定对象.实例方法(a,b...) |
引用某类对象的实例方法 | 类名::实例方法名 | 函数式接口中被实现的方法的第一个参数做为调用者,后面的参数传给该方法做为参数 | (a,b,c...)->a.实例方法(b,c...) |
引用构造器 | 类名::new | 函数式接口中被实现的方法的所有参数传给该构造器做为参数 | (a,b...)->new 类名(a,b...) |
##4.1 引用类方法 |
@FunctionalInterface interface Converter { Integer convert(String form); } public class ConverterTest { public static void main(String[] args) { //Lambda表达式只有一条语句,能够省略1花括号:Lambda表达式会把这条代码的值做为返回值 Converter c=(form)->Integer.parseInt(form); System.out.println(c.convert("185")); //下面经过引用类方法来实现相同的功能 Converter cPlus=Integer::valueOf; System.out.println(cPlus.convert("140")); } }
##4.2 引用特定对象的实例方法
@FunctionalInterface interface Converter { Integer convert(String form); } public class ConverterTest1 { public static void main(String[] args) { //先使用Lambda表达式来建立一个Converter对象 Converter c=form->"fkit.org".indexOf(form);//代码块只有一条语句,所以Lambda表达式会把这条代码的值做为返回值 System.out.println(c.convert("it"));//输出2 //引用特定对象的特定方法 "fkit.org"是一个String对象 Converter c1="fkit.org"::indexOf; System.out.println(c1.convert("org"));//输出5 } }
对于上面的示例方法引用,也就是说,调用"fkit.org"对象的indexOf()实例方法来实现Converter函数式接口中惟一的抽象方法,当调用Converter接口中的惟一抽象的方法时,调用参数会传给"fkit.org"对象的indexOf()实例方法。 ##4.3 引用某类对象的实例方法 先介绍一个函数:public String substring(int beginIndex, int endIndex)返回字符串索引范围[beginIndex,endIndex)的子字符串。
@FunctionalInterface interface MyTest { String test(String a,int b, int c); } class substringTest { public static void main(String[] args) { MyTest m=(a,b,c)->a.substring(b,c); System.out.println(m.test("fkjava",1,5)); //引用某类对象的实例方法 MyTest mPlus=String::substring; System.out.println(mPlus.test("hello world",2,7));//至关于"hello world".substring(2,7) } } ---------- 运行Java捕获输出窗 ---------- kjav llo w 输出完成 (耗时 0 秒) - 正常终止
##4.4 引用构造器 JFrame屏幕上window的对象,可以最大化、最小化、关闭
import java.awt.*; import javax.swing.*; @FunctionalInterface interface YourTest { JFrame win(String title); } public class MethodRefer { public static void main(String[] args) { // 下面代码使用Lambda表达式建立YourTest对象 // YourTest yt = (String a) -> new JFrame(a); // 构造器引用代替Lambda表达式。 // 函数式接口中被实现方法的所有参数传给该构造器做为参数。 YourTest yt = JFrame::new; JFrame jf = yt.win("个人窗口"); System.out.println(jf); } }
#5、Lambda表达式和匿名内部类的联系和区别 Lambda表达式与匿名内部类之间存在以下相同点: 一、Lambda表达式与匿名内部类同样,均可以直接访问"effectively final"的局部变量,以及外部类的成员变量,包括实例变量和类变量。 二、Lambda表达式建立的对象与匿名内部类生成的对象同样,均可以直接从接口中继承的默认方法。
@FunctionalInterface interface Displayable { void display(); default int add(int a,int b) { return a+b; } } public class LambdaAndInner { private int age=12; private static String name="fkit.org"; public void test() { var book="疯狂Java讲义"; Displayable dis=()->{ //访问"effictively final"的局部变量 System.out.println("book局部变量为:"+book); //访问外部类的实例变量和类变量 System.out.println("外部类的age实例变量:"+age); System.out.println("外部类的name类变量:"+name); }; dis.display(); //调用方对从接口继承add()方法 System.out.println(dis.add(3,5)); } public static void main(String[] args) { var lambda=new LambdaAndInner(); lambda.test(); } } ---------- 运行Java捕获输出窗 ---------- book局部变量为:疯狂Java讲义 外部类的age实例变量:12 外部类的name类变量:fkit.org 8 输出完成 (耗时 0 秒) - 正常终止
与匿名函数类似的是,因为Lambda表达式访问了book局部变量,所以该局部变量至关于有一个隐式的final修饰,所以一样不容许对book局部变量从新赋值。当程序使用了Lambda表达式建立了Displayable对象以后,该对象不只可调用接口的抽象方法,也能够调用接口中的默认方法,所以一样不容许对book局部变量从新赋值。 </font color=red>Lambda表达式与匿名内部类的区别: 一、匿名内部类能够为内部类能够为任意接口建立实例;但Lambda表达式只能为函数式建立实例。 二、匿名内部类能够为抽象类乃至普通类建立实例;但Lambda表达式只能为函数式接口建立实例。 三、匿名内部类是实现抽象方法的方法体容许调用接口中定义的默认方法;但Lambda表达式的代码块不容许不容许调用接口中的默认方法。</font>
#6、使用Lambda表达式调用Arrays的类方法 Arrays类的有些方法须要Comparator、XxxOperator、XxxFunction等接口的实例,这些接口都是函数式编程,所以可使用Lambda表达式来调用Arrays的方法。
import java.util.Arrays; public class LambdaArrays { public static void main(String[] args) { var arr1 = new String[] {"java", "fkava", "fkit", "ios", "android"}; Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length()); //这行Lambda表达式的目标类型是Comparator,该Comparator指定判断字符串大小的标准:字符串越长,认为该字符串越大 System.out.println(Arrays.toString(arr1)); var arr2 = new int[] {3, -4, 25, 16, 30, 18}; // left表明数组中前一个所索引处的元素,计算第一个元素时,left为1 // right表明数组中当前索引处的元素 Arrays.parallelPrefix(arr2, (left, right)-> left * right); //这行Lambda表达式的目标类型是IntBinaryOperator,该对象将会根据先后两个元素来计算当前元素 System.out.println(Arrays.toString(arr2)); var arr3 = new long[5]; // operand表明正在计算的元素索引 Arrays.parallelSetAll(arr3, operand -> operand * 5); //这行Lambda表达式的目标类型是IntToLongFunction,该对象将会根据当前索引值计算当前元素的值。 System.out.println(Arrays.toString(arr3)); } } ---------- 运行Java捕获输出窗 ---------- [ios, java, fkit, fkava, android] [3, -12, -300, -4800, -144000, -2592000] [0, 5, 10, 15, 20] 输出完成 (耗时 0 秒) - 正常终止