前面一篇博客我们已经说到了,lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。现在我们来写一段java的命令者模式来自己研究下lambda表达式的语法。
这里重复下命令者模式:
考虑这么一个情景,某个方法需要完成一个行为,但是这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。举个例子,我有个方法需要遍历某个数组的数组元素,但是无法确定在遍历这个数组元素时如何处理这些元素,需要在实际调用该方法时候指定具体的处理行为。也就是说我现在要处理一些参数,但是具体的这些参数的处理方式我不确定,我想要写一个方法,即可以传入一些参数,也可以传入这些参数的处理方式,怎么办呢?使用命令者模式,因为java8之前java不允许传一段代码进一个方法。
下面是原来的代码:
public class Test { //处理数组 public void process(int[] array, Command cmd) { cmd.process(array); } public static void main(String[] args) { Test test = new Test(); int[] target = { 1, 2, 3 }; test.process(target, new Command() { @Override public void process(int[] array) { for (int i : target) { System.out.println(i); } } }); } } interface Command { //处理数组的行为 void process(int[] array); }
现在我们使用lambda表达式改一下上面的代码:
public class Test { //处理数组 public void process(int[] array, Command cmd) { cmd.process(array); } public static void main(String[] args) { Test test = new Test(); int[] target = { 1, 2, 3 }; test.process(target, (int[] array) -> { for (int i : target) { System.out.println(i); } }); } } @FunctionalInterface interface Command { //处理数组的行为 void process(int[] array); }
从上面的代码我们可以看出,我们使用lambda表达式不需要再写new XXX(){}这种繁琐的代码了呢,现在不需要指出重写方法的名字,也不需要给出重写的方法的返回值类型,只要给出重写的方法括号以及括号里面的形参列表就可以。其实这里也说出lambda表达式的主要作用,就是代替匿名内部类的繁琐语法。
lambda表达式有三部分组成:
1),形参列表。形参列表允许省略形参类型,如果形参只有一个,甚至连新参列表的圆括号也可以省略。
2),箭头。->。
3),代码块。如果代码块只包含一条语句,可以省略花括号。如果代码块只有一条return语句,可以省略return关键字。如果lambda表达式需要返回值,而它的代码块中也仅有一条省略了return的语句,lambda表达式会自动返回这条语句的值。
下面实际编码来演示下lambda表达式的语法:
public class Test { public void testA(A a) { System.out.println(a); a.test(); } public void testB(B b) { System.out.println(b); b.test("111"); } public void testC(C c) { System.out.println(c); System.out.println("这里是接口C的表达式:" + c.test(1, 2)); } public static void main(String[] args) { Test test = new Test(); test.testA(() -> { System.out.println("这里是接口A的lambda表达式"); }); //上面的代码块只有一行代码,可以简写 test.testA(() -> System.out.println("这里是接口A的lambda表达式")); test.testB((str) -> System.out.println("这里是接口B的lambda表达式" + str)); //上面的形参参数只有一个,可以简写 test.testB(str -> System.out.println("这里是接口B的lambda表达式" + str)); test.testC((a, b) -> { return (a + b); }); //上面的代码块只有一行代码,可以省略花括号,也可以省略return语句 test.testC((a, b) -> a + b); } } @FunctionalInterface interface A { void test(); } @FunctionalInterface interface B { void test(String str); } @FunctionalInterface interface C { int test(int a, int b); }
这里的代码可以正常的编译和运行,说明lambda表达式实际上将会被当成一个任意类型的对象,到底需要当成何种类型的对象,这取决于运行环境的需要。下一篇博客我会整理到函数式接口,到时候就清楚了。
在上面的使用过程中,我们看到了:
1),我们在使用lambda表达式的时候,参数的类型可以被推导出来,当然我们也可以自己添加上参数的类型或者注解或者其他的修饰符。如果需要显式指定一个参数的类型,那么必须为所有的参数声明类型。比如:
(int a, int b)->(a%b)==0是合法的
(int a, b)->(a%b)==0是不合法的
2),永远不需要为一个lambda表达式执行返回类型,它总是可以从上下文中被推导出来。比如:
(String first,String second)->Integer.compare(first.length(),second.length());可以被使用在期望结果类型是int的上下文中。
3),在lambda表达式中,只在某些分支中返回值,其他分支没有返回值是不合法的。比如下面的代码:
public class Test { public static void main(String[] args) { A a = (b) -> { if (b > 0) { return 1; } }; } } @FunctionalInterface interface A { int test(int a); }
4),为了在目标类型上下文中使用lambda表达式,抽象方法的类型和表达式的类型必须兼容。具体来说,lambda表达式的参数的类型和数量必须与方法的参数兼容,返回类型必须兼容,并且lambda表达式可能抛出的异常也必须能被方法接受。
5),lambda表达式的方法体与嵌套代码块有着相同的作用域。因此他也适用同样的命名冲突和屏蔽规则。在lambda表达式中不允许声明一个与局部变量同名的参数或者局部变量。比如下面代码报错:
int a; test.testC((a, b) ->a + b);