二.行为参数化的Lambda形式 Java8在推出行为参数化的同时也提供了Lambda表达式,那么Lambda表达式是什么呢?为什么要用Lambda表达式呢?
在上一篇中我们写到的例子,就可以用Lambda来实现,我们一起来看看:
public class one{ public static void main(String[] args){ one one = new one(); /*show(one::otherMethod ,"hello world");*/ show(n->{System.out.println(n);}, "hello Lambda"); } /*public void otherMethod(T t){ int i = 0; System.out.println(i + t.toString()); }*/ public static void show(Consumer consumer,String text){ consumer.accept(text); }}复制代码
如果对注释有疑问的话,请看上一篇文章哦
那么我们在调用show方法时又和上一篇文章(也就是第一个注释处)不同,我们没有传入方法引用,而是传入了...好吧,这就是Lambda表达式,上面在调用show方法传入的第一个参数:n->{System.out.println(n);} 就是本章要讲的Lambda表达式,它由一个参数列表和方法体组成,参数列表和方法体由 -> 隔开,在这里我们用的还是Consumer接口,再上一章的Consumer接口源码中我们看到它的抽象方法accept中接受一个泛型参数,那么我们现在传入的Lambda表达式它的参数列表也是一个参数,然后在方法体中打印输出这个参数(如果n是引用型变量的话先调用toString()方法再输出)然后show方法接收这个Lambda表达式给了第一个形参Consumer consumer并在show方法中调用了consumer对象的accept方法。
那么为什么Lambda表达式可以传递给一个函数式接口的引用呢?
其实,Lambda表达式就是一个实现了函数式接口的匿名类对象,在上面的Lambda表达式中我们用 n->{System.out.println(n);} 这个Lambda表达式的时候就等于创建了一个匿名类对象实现了Consumer接口并重写了Consumer的accept方法,accept方法的内容就是Lambda表达式的方法体。
我们现在来说说Lambda表达式的书写规则:刚才说到它由一个参数列表和一个方法体组成并由 -> 隔开,参数列表由 ( ) 括起来,方法体由 { } 括起来,等等,明明上面的Lambda表达式没有用 ( ) 括起来啊!嘿嘿,那是因为上面的Lambda表达式重写的accept方法只有一个参数,所以它只用传入一个参数。当Lambda表达式的参数列表只有一个参数时,那么 ( ) 可以省略。并且Lambda表达式的方法体也可以不用 { } 括起来,而是用表达式代替。
就比如:
(m,n) -> {return m>n;} 可以用 (m,n) -> m>n 代替() -> {return "hello Lambda";} 可以用 () -> "hello Lambda" 代替复制代码
那么我们应该在哪里应用Lambda表达式呢? 就如上面代码所示,Lambda应该用在函数式接口上,当我们实现一个函数式接口的时候就可以用Lambda表达式 。
上面的代码也可以这样写:
public class one{ public static void main(String[] args){ /*one one = new one();*/ /*show(one::otherMethod ,"hello world");*/ /*show(n->{System.out.println(n);}, "hello Lambda");*/ Consumer consumer = (n)->{System.out.println(n);}; consumer.accept("hello Lambda"); } /*public void otherMethod(T t){ int i = 0; System.out.println(i + t.toString()); }*/ /*public static void show(Consumer consumer,String text){ consumer.accept(text); }*/}复制代码
我将原来的代码全部注释,只写了一个Consumer引用并将一段Lambda表达式赋值给它,就等于是写了一类实现了Consumer接口并创建了对象,这个对象就是consumer,然后调用它的accept方法。
一定要注意Lambda表达式只能用于实现函数是接口,Java提供了很多函数式接口在上一章我有提到过,在最后我还说到了函数描述符,在设立我再写一遍:
函数式接口 函数描述符 Predict T -> boolean Consumer T -> void Function<T , R> T -> R Supplier () -> T
UnaryOperator(T , T) -> TBinaryOperator (T , T) -> TBiPredict (L , R) -> booleanBiConsumer (T , U) -> voidBiFunction (T , U) -> R复制代码
以上是常用的几个函数式接口,那么我们能发现函数描述符的方法签名就是Lambda表达式的方法签名。函数描述符就是一种描述Lambda和函数式接口的签名。
细心的人去看了这些函数式接口的源码后可能会发现它们有一个共同点:它们的抽象方法没有抛出异常,也就是说这这些函数式接口中我们没有办法抛出异常,只能捕获。就像是我写一个读取文件内容的Lambda表达式的话只能向下面这样:
public class one{ public static void main(String[] args){ Consumer consumer = path -> {File file = new File(path); try { BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); } catch (FileNotFoundException e) { e.printStackTrace(); } }; }}复制代码
因为Consumer的accept方法并没有声明抛出FileNotFoundException异常,所以所有需要捕获或者抛出异常的地方只能捕获,不能抛出,但是我们常常在写代码的时候希望很多底层功能产生的异常不要在底层捕获,而是全部抛出,在最上层一并捕获并进行处理。这个时候我们就要定义自己的 函数式接口,就像上面的读取文件Lambda表达式,如果我们希望抛出异常的话,那么就要这样做:
首先定义一个函数式接口,并在抽象方法处声明抛出异常
import java.io.FileNotFoundException;@FunctionalInterfacepublic interface MyInterface { public void read(String path) throws FileNotFoundException;}复制代码
然后在Lambda表达式里直接上代码,不需要声明 try{}catch(){} 块
public class one{ public static void main(String[] args){ MyInterface myInterface = path -> {File file = new File(path); BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); }; }}复制代码