Java8 lambda 要注意的四个地方

Java8 往函数式编程迈入了一大步;引入了函数式接口的概念;并使用 lambda 表达式在语法上大大简化函数式接口写法;

帖上一段简单的示例代码:

Runnbale run = () -> System.out.println("Hello Jack!");

new Thread(run).start();  

lambda可以粗暴看作为匿名类的实现,提供了轻量级的语法上的实现,但远不止如此,比喻还有改变 this 的指向;

在使用 lambda 的时候有几个要注意的地方
  • 声明:函数式接口是指只有一个函数的接口,可以使用@FunctionalInterface注解来进行显示声明,这样编译器会验证接口是否满足函数接口的要求;也可以不使用@FunctionalInterface来显示声明,接口满足只有一个函数的要求即可,不包含有default默认实现的函数;

  • 类型推导:使用lambda的时候,简化的语法形式对指向类型变得泛化无法明确,对后续程序执行变得不安全,编译器自动根据使用上下文所期待的类型(即目标类型)来进行推导出来的具体函数式接口;例如我写了两个lambda表达式;

Callable<String> callable = () -> "hello";  
PrivilegedAction<String> a = () -> "world";  

第一个就是 Callbale的实例,第二个是PrivilegedAction的实例;

但是要注意是:

Object o = () -> { System.out.println("hello"); };  

这样的代码是非法的,编译无法通过,因为后面的lambda表达式无法根据目标类型推荐出是哪个函数接口,如像下面做一个强制转换,才能编译通过;

Object o = (Runnable) () -> { System.out.println("hello"); };  
  • 语法作用域this 不再如内部类那向指向内部类自己,而是当前类的执行环境对象,并没有引用新的作用域,这样使得 lamada 表达式在形式上更加直接,与上下文代码保持一致;如下代码示例:
public class Hello {

    Runnable runnable = () -> { System.out.println(this); };

    public String toString() {  
        return "I am 'Hello'"; 
    }

    public static void main(String[] args) {
        new Hello().runnable.run();
    }
}

执行结果:

"I am 'Hello'"
  • 外部的变量和值:在Java1.8之前,匿名内部类要访问外部的成员变量,需要将外部的成员变量使用final进行修饰,即从语法上强制要求内部类不能对外部的变量进行赋新值;Java1.8lambda从语法上放开了这个限制,即不用加final修饰变量,lambda表达式(或者匿名内部类中)都可以自由的访问变量的值,但试图对外部的变更进行赋值仍然是不被允许的,因为这是引起『race condition』,在多线程情况下会引起程序出现不可预测的错误。