JDK 8 对 Lambda 函数编程的支持,浅的来说无非是引入了一些新的语法结构,是继JDK5 引入的Generics后又一项对大家编码方式的一种革新,如果你不跟上的话,恐怕过段时间,你会认为Java代码成了火星语。深的来说,Java是在语言级进一步支持多核CPU的环境下的并行处理,这在Stream API 中可见一斑,在Java之前,已经有很多主流语言,像 C#和C++,支持Lambda 函数编程,此次Java引入Lambda支持也算后知后觉了。
想在Java中书写Lambda的代码,有两个东东要了解。一个是Lambda 表达式。另一个就是函数接口。这篇文章主要是针对这两点进行展开。
- Lambda表达式 - Lambda表达式其实就是一个匿名的方法。这个方法还不能自己执行,它只能用来实现定义在函数接口中方法,当Lambda 表达式实现一个函数接口时会产生一个匿名类。
- 函数接口 - 函数接口是一个包含且只包含一个抽象方法的接口,在JDK 8 之前,定义在接口中的方法都是抽象方法。从JDK 8 之后,接口中定义的方法可以使用default 关键字为接口方法定义默认实现。一旦定义了默认实现,就不能叫做抽象方法了。Java之前定义的Runnable,Comparable接口都可以称为函数接口。Lambda表达式在Java里脱离了函数接口,就只能算一个不能执行的表达式而已。直到Lambda表达式被用来实现某个函数接口,才能在Java 世界里运行。
让我们结合函数接口来分析一下Lambda表达式。
Lambda 表达方式可以表示为 (p1,p2,….) -> 简单表达式|代码块
其中 -> 是 Lambda 操作符,左侧是表达式需要的参数列表,用逗号分隔,如果没有参数的话,就是一个空括号()。 右侧是表达式的具体内容,分为简单表达式和包含复杂逻辑的代码块两种。 简单表达式只能包含一个单一的表达式,但代码块就是一个匿名代码块,能包含更复杂的逻辑。
我们来看两个简单表达式的例子吧
()-> “Hello, Lambda”
这个简单的例子不接受任何参数,所以左边只有一个括号。右边是一个返回简单字符串的表达式。类似于我们以前写的如下Java 代码
String sayHello(){ Return “Hello, Lambda”; }
第一个函数接口与Lambda表达式结合的例子,能够运行。
interface Hello4Lambda { public String sayHello(); } public class HelloLambda { public static void main(String[] args){ Hello4Lambda hl = () -> "Hello,Lambda."; System.out.println(hl.sayHello()); } }
再看一个带参数的例子
(name) -> “Hello, ” + name
这个Lambda表达式相当于如下Java 代码
String sayHello(String name){ Return “Hello, ” + name; }
可运行的代码如下
interface Hello4Anyone { public String sayHello(String name); } public class HelloAnyone { public static void main(String[] args){ Hello4Anyone hw = (name) -> "Hello," + name; System.out.println(hw.sayHello("Matt")); } }
你可能发现Lambda表达式里的参数并没有指定参数的类型。Java会自动根据该Lambda表达式所实现的函数接口自行推演出参数类型。对于简单表达式,是一定要默认返回运算结果的。这就意味着被实现的函数接口必须有返回值而不能是void. 如下代码会报编译错误,因为接口定义的方法是一个没有返回值的接口方法。
interface Hello4Anyone { public void sayHello(String name); } public class HelloAnyone { public static void main(String[] args){ Hello4Anyone hw = (name) -> "Hello," + name; System.out.println(hw.sayHello("Matt")); } }
让我们看看带有默认实现的例子,这个例子会发生编译错误,因为我们定义的接口不是一个函数接口,没有抽象方法。
interface Hello4Anyone { public default String sayHello(String name){ //默认实现 return "Hello, " + name; }; } public class HelloAnyone { public static void main(String[] args){ Hello4Anyone hw = (name) -> "Hello," + name;//编译错误发生 System.out.println(hw.sayHello("Matt")); } }
Lambda表达式中复杂代码块可以让我们完成比简单表达式更复杂的操作,我们只需要将代码置于{}之中,并要在代码的最后显示地加上return关键字将计算结果返回,当然是你真地需要返回结果的情况下。如果你试图实现的函数接口不需要返回任何返回值,那么你的Lambda代码块里也就不需要return 返回任何结果。
interface DoubleCalculate { double Sum(double[] arr); } public class DoubleCalculator { public static void main(String[] args){ double[] a = new double[10]; for (int i = 0 ;i< a.length;i++) a[i] = Math.random() * 100; DoubleCalculate dc = (arr) -> { double sum = 0; for (int j = 0;j<arr.length;j++){ sum += arr[j]; } return sum; }; System.out.println(dc.Sum(a)); } }
这个例子是对一个double数组内的元素进行求和。我们使用Lambda表达式的代码块来实现该逻辑。但如果我们试图想对int 数组进行求和时,接口DoubleCalculate时无法接收int类型数组。为此,我们可能还要另外定义一个接口函数专门针对int类型数组。
interface IntCalculate { int Sum(int[] arr); } public class IntCalculator { public static void main(String[] args){ int[] a = new int[10]; for (int i = 0 ;i< a.length;i++) a[i] = (int)(Math.random() * 100); IntCalculate dc = (arr) -> { int sum = 0; for (int j = 0;j<arr.length;j++){ sum += arr[j]; } return sum; }; System.out.println(dc.Sum(a)); } }
这样代码会显得很冗余,那我们能否使用范型将函数接口中抽象方法的参数范化,这样不就可以了?答案时肯定的,JDK 5 范型的支持,同样可以应用在函数接口上。
interface Calculate<T> { T Sum(T[] arr); } public class GenericCalculator { public static void main(String[] args){ //For double array . Double[] a = new Double[10]; for (int i = 0 ;i< a.length;i++) a[i] = Math.random() * 100; Calculate<Double> dc = (arr) -> { double sum = 0; for (int j = 0;j<arr.length;j++){ sum += arr[j]; } return sum; }; System.out.println(dc.Sum(a)); //For int array . Integer[] b = new Integer[10]; for (int i = 0 ;i< b.length;i++) b[i] = (int)(Math.random() * 100); Calculate<Integer> ic = (arr) -> { int sum = 0; for (int j = 0;j<arr.length;j++){ sum += arr[j]; } return sum; }; System.out.println(ic.Sum(b)); } }
但对于Lambda表达式本身是无法支持范型的,我们看到了两个不同的Lambda 表达式的代码块分别针对Double 和Integer.