在2013年将发布的 JavaSE8 中将包含一个叫做 Lambda Project 的计划,在今年6月份的 JSR-335 草案中有描述。
JSR-335 将闭包引入了 Java 。闭包在现在的很多流行的语言中都存在,例如 C++、C# 。闭包允许我们创建函数指针,并把它们作为参数传递。在这篇文章中,我们将粗略的看一遍Java8的特性,并介绍Lambda表达式。而且我将试着放一些样例程序来解释一些概念和语法。
Java 编程语言给我们提供了接口的概念,接口里可以定义抽象的方法。接口定义了 API,并希望用户或者供应商来实现这些方法。很多时候,我们并不为一些接口创建独立的实现类,我们通过写一个匿名内部类来写一个内联的接口实现。
匿名类使用的非常广泛。匿名内部类使用的最常见的场景就是事件处理器了。其次匿名内部类还常被用在多线程的程序中,我们通常写匿名内部类,而不是创建 Runnable/Callable 接口的实现类。
就像我们讨论的一样,一个匿名类就是一个内联的给定的接口的实现。通常我们将这个实现类的对象作为参数传递给一个方法,然后这个方法将在内部调用传递过来的实现类的方法。故这种接口叫做回调接口,这些方法叫做回调方法。
虽然匿名类到处都在使用,但是他们还是有很多问题。第一个主要问题是复杂。这些类让代码的层级看起来很乱很复杂,也称作 Vertical Problem 。第二,他们不能访问封装类的非 final 成员。this 这个关键字将变得很有迷惑性。如果一个匿名类有一个与其封装类相同的成员名称,内部变量将会覆盖外部的成员变量,在这种情况下,外部的成员在匿名类内部将是不可见的,甚至不能通过 this 关键字来访问。因为 this 关键字值得是匿名类对象本身而不是他的封装类的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void anonymousExample() { String nonFinalVariable = 'Non Final Example' ; String variable = 'Outer Method Variable' ; new Thread( new Runnable() { String variable = 'Runnable Class Member' ; public void run() { String variable = 'Run Method Variable' ; //Below line gives compilation error. //System.out.println('->' + nonFinalVariable); System.out.println( '->' + variable); System.out.println( '->' + this .variable); } }).start(); } |
输出是:
这个例子很好的说明了我上面所说的这个问题,而 Lambda 表达式几乎解决了匿名内部类带来的所有问题。在我们进一步探讨 lambda 表达式之前,让我们来看一看 Functional Interfaces。
Functional Interfaces
Functional Interfaces 是一个只有单个方法的接口,这代表了这个方法契约。
上面的定义中的只有一个实际上并没有那么简单。这段有些不懂,请读者查看原文(The ‘Single’ method can exist in the form of multiple abstract methods that are inherited from superinterfaces. But in that case the inherited methods should logically represent a single method or it might redundantly declare a method that is provided by classes like Object, e.g. toString.)
下面的例子清楚的展示了怎样理解 Functional Interfaces 的概念。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Runnable { void run(); } // Functional interface Foo { boolean equals(Object obj); } // Not functional; equals is already an implicit member interface Bar extends Foo { int compare(String o1, String o2); } // Functional; Bar has one abstract non-Object method interface Comparator { boolean equals(Object obj); int compare(T o1, T o2); } // Functional; Comparator has one abstract non-Object method interface Foo { int m(); Object clone(); } // Not functional; method Object.clone is not public interface X { int m(Iterable arg); } interface Y { int m(Iterable arg); } interface Z extends X, Y {} // Functional: two methods, but they have the same signature |
大多数回调接口都是 Functional Interfaces。例如 Runnable,Callable,Comparator 等等。以前被称作 SAM(Single Abstract Method)
Lambda 表达式
我们上边说过,匿名类的一个主要问题是是代码的层级看起来很乱,也就是 Vertical Problem 了,Lamdba 表达式实际上就是匿名类,只不过他们的结构更轻量,更短。Lambda 表达式看起来像方法。他们有一个正式的参数列表和这些参数的块体表达。
上面的例子的意思是,第一个表达式接收一个 String 变量作为参数,然后返回字符串的长度。第二个不带任何参数,并返回43。最后,第三个接受两个整数 x 和 y ,并返回其和。
在看了许多文字后,终于,我可以给出第一个 Lambda 表达式的例子了,这个例子运行在 JavaSE8 的预览版下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class FirstLambdaExpression { public String variable = 'Class Level Variable' ; public static void main(String[] arg) { new FirstLambdaExpression().lambdaExpression(); } public void lambdaExpression(){ String variable = 'Method Local Variable' ; String nonFinalVariable = 'This is non final variable' ; new Thread (() -> { //Below line gives compilation error //String variable = 'Run Method Variable' System.out.println( '->' + variable); System.out.println( '->' + this .variable); }).start(); } } |
输出是:
你可以比较一些使用 Lambda 表达式和使用匿名内部类的区别。我们可以清楚的说,使用 Lambda 表达式的方式写匿名类解决了变量可见性的问题。你可以看一下代码中的注释, Lambda 表达式不允许创建覆盖变量。
通常的 Lambda 表达式的语法包括一个参数列表,箭头关键字'->'最后是主体。主体可以是表达式(单行语句)也可以是多行语句块。如果是表达式,将被计算后返回,如果是多行的语句块,就看起来跟方法的语句块很相似了,可以使用 return 来指定返回值。break 和 continue 只能用在循环内部。
为什么选择这个特殊的语法形式呢,因为目前 C# 和 Scala 中通常都是这种样式,也算是 Lambda 表达式的通用写法。这样的语法设计基本上解决了匿名类的复杂性。但是与此同时他也是非常灵活的,例如,如果方法体是单个表达式,大括号和 return 语句都是不需要的。表达式的结果就是作为他自己的返回值。这种灵活性可以保持代码简洁。
Lambda 表达式用作匿名类,因此他们可以灵活运用在其他模块或在其他 Lambda 表达式(嵌套的 Lambda 表达式)。
1 2 3 4 5 6 7 8 | //Lambda expression is enclosed within methods parameter block. //Target interface type is the methods parameter type. String user = doSomething(() -> list.getProperty(“propName”); //Lambda expression is enclosed within a thread constructor //target interface type is contructors paramter i.e. Runnable new Thread (() -> { System.out.println( 'Running in different thread' ); }).start();<span style= 'font-family:'sans serif, tahoma, verdana, helvetica';font-size:x-small;' ><span style= 'line-height:19px;white-space:normal;' > </span></span> |
如果你仔细看看 lambda 表达式,您将看到,目标接口类型不是一个表达式的一部分。编译器会帮助推断 lambda 表达式的类型与周围环境。
Lambda 表达式必须有一个目标类型,而他们可以适配任意可能的目标类型。当目标类型是一个接口的时候,下面的条件必须满足,才能编译正确:
而且,如果目标类型中声明的方法只接收一个参数(很多时候都是这样的),那么参数的小括号也是可以不写的,例如:
一个很明显的问题来了,为什么 Lambda 表达式不需要一个指定的方法名呢?
答案是:Lambda 表达式只能用于 functional interface ,而 functional interface 只有一个方法。
当我们确定一个 functional interface 来创建 Lambda 表达式的时候,编译器可以感知 functional interface 中方法的签名,并且检查给定的表达式是否匹配
这种灵活的语法帮助我们避免了使用匿名类的 Vertical Problem ,而且不会带来 Horizontal Problem(单行语句非常长)。
Lambda 表达式的语法是上下文相关的,但是这些并不是第一次出现。Java SE 7添加的diamond operators 也有这个概念,通过上下文推断类型。
1 2 3 4 | void invoke(Runnable r) {r.run()} void Future invoke(Callable r) { return c.compute()} //above are two methods, both takes parameter of type functional interface Future s = invoke(() -> 'Done' ); //Which invoke will be called?<span style='font-family:'sans serif, tahoma, verdana, helvetica';font-size:x-small;'><span style='line-height:19px;white-space:normal;'> </span></span> |
上面问题的答案是调用接收Callable参数的方法。在这种情况下编译器会通过不同参数类型的重载解决。当有不止一个适用的重载方法,编译器也检查lambda表达式与相应的目标类型的兼容性。简单的说,上面的invoke方法期望一个返回,但是只有一个invoke方法具有返回值。
Lambda表达式可以显式的转换为指定的目标类型,只要跟对应的类型兼容。看一下下面的程序,我实现了三种Callable,而且都将其转换为Callable类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class FirstSightWithLambdaExpressions { public static void main(String[] args) { List list = Arrays.asList( (Callable)()-> 'callable 1' , (Callable) ()-> 'callable 2' , (Callable) ()-> 'callable 3' ); ExecutorService e = Executors.newFixedThreadPool( 2 ); List futures = null ; try { futures = e.invokeAll(list); new FirstSightWithLambdaExpressions().dumpList(futures); } catch (InterruptedException | ExecutionException e1) { e1.printStackTrace(); } e.shutdown(); } public void dumpList(List list) throws InterruptedException, ExecutionException { for (Future future : list) { System.out.println(future.get()); } } } |
正如我们前面讨论的一样,匿名类不能访问周围环境中非final的变量。但是Lambda表达式里就没有这个限制。
目前,该定义的 functional interfaces 只适用于接口。我试着对一个只有一个抽象方法的抽象类创建一个 lambda 表达式,但出了一个编译错误。按照 jsr - 335,未来版本的 lambda 表达式可能支持 Functional Classes。
方法引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class MethodReference { public static void main (String[] ar){ Employee[] employees = { new Employee( 'Nick' ), new Employee( 'Robin' ), new Employee( 'Josh' ), new Employee( 'Andy' ), new Employee( 'Mark' )}; System.out.println( 'Before Sort:' ); dumpEmployee(employees); Arrays.sort(employees, Employee::myCompare); System.out.println( 'After Sort:' ); dumpEmployee(employees); } public static void dumpEmployee(Employee[] employees){ for (Employee emp : Arrays.asList(employees)){ System.out.print(emp.name+ ', ' ); } System.out.println(); } } class Employee { String name; Employee(String name) { this .name = name; } public static int myCompare(Employee emp1, Employee emp2) { return emp1.name.compareTo(emp2.name); } } |
输出是:
现在,我执行下面的两行代码
输出是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class ConstructorReference { public static void main(String[] ar){ EmlpoyeeProvider provider = Employee:: new ; Employee emp = provider.getMeEmployee( 'John' , 30 ); System.out.println( '->Employee Name: ' +emp.name); System.out.println( '->Employee Age: ' +emp.age); } } interface EmlpoyeeProvider{ Employee getMeEmployee(String s, Integer i); } class Employee{ String name; Integer age; Employee (String name, Integer age){ this .name = name; this .age = age; } } |
输出是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class DefaultMethods { public static void main(String[] ar){ NormalInterface instance = new NormalInterfaceImpl(); instance.myNormalMethod(); instance.myDefaultMethod(); } } interface NormalInterface{ void myNormalMethod(); void myDefaultMethod () default { System.out.println( '-> myDefaultMethod' ); } } class NormalInterfaceImpl implements NormalInterface{ @Override public void myNormalMethod() { System.out.println( '-> myNormalMethod' ); } }<span style= 'font-family:'sans serif, tahoma, verdana, helvetica';font-size:x-small;' ><span style= 'line-height:19px;white-space:normal;' > </span></span> |
输出是:
1 2 3 4 5 6 7 8 9 10 11 12 | interface ParentInterface{ void initiallyNormal(); void initiallyDefault () default{ System.out.println( '-> myDefaultMethod' ); } } interface ChildInterface extends ParentInterface{ void initiallyNormal() default{ System.out.println( 'now default - > initiallyNormal' ); } void initiallyDefault (); //Now a normal method } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class DefaultMethods { public static void main(String[] ar){ Interfaxe impl = new NormalInterfaceImpl(); impl.defaultMethod(); } } class ParentClass{ public void defaultMethod() { System.out.println( '->ParentClass' ); } } interface Interfaxe{ public void defaultMethod() default { System.out.println( '->Interfaxe' ); } } class NormalInterfaceImpl extends ParentClass implements Interfaxe{}<span style= 'font-family:'sans serif, tahoma, verdana, helvetica';font-size:x-small;' ><span style= 'line-height:19px;white-space:normal;' > </span></span> |
输出是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class DefaultMethods { public static void main(String[] ar){ FirstInterface impl = new NormalInterfaceImpl(); impl.defaultMethod(); } } interface FirstInterface{ public void defaultMethod() default { System.out.println( '->FirstInterface' ); } } interface SecondInterface{ public void defaultMethod() default { System.out.println( '->SecondInterface' ); } } class NormalInterfaceImpl implements FirstInterface, SecondInterface{ public void defaultMethod(){ SecondInterface. super .defaultMethod(); } } |
联系客服