Java8 新特性详解-Lambda 表达式

分享 青牛 ⋅ 于 2016-12-05 19:32:22 ⋅ 最后回复由 xd502djj 2016-12-14 14:06:55 ⋅ 4941 阅读

什么是Lambda表达式

Lambda表达式基于数学中的λ演算得名,lambda表达式也可以叫做匿名函数,是指一类无需定义标识符(函数名)的函数或子程序。匿名函数最早是由LISP语言提出,后续好多语言都支持此特性。JAVA语言是面向对象语言,号称一切皆为对象,对函数式编程的一些特性一直没有支持。但随着现在语言环境的发展,scala/python/ruby/c++等等发展,java也被感动了,java8也支持了lambda表达式,使java语言又有了新的活力。

我们先看一段代码例子,lambda表达式的样子。

java8以前的代码:

//创建一个线程,打印一句话
Thread thread = new Thread(new Runnable(){
  public void run(){
    System.out.println("Hello Lambda");
  }

});

//对整数数组排序
Integer[] array = {1,9,8,38,3,9,7,12,30};   
Arrays.sort(array, new Comparator<Integer>(){
  @Override
  public int compare(Integer i1, Integer i2) {
    return i1.compareTo(i2);
  }
});

java8使用lambda表达式的代码:

//创建一个线程,打印一句话
Thread thread = new Thread(() -> System.out.println("Hello Lambda"));

//对整数数组排序
Integer[] array = {1,9,8,38,3,9,7,12,30};   
Arrays.sort(array,(i1,i2) -> i1.compareTo(i2));

java8 使用lambda表达式是不是更简短明了?感觉心情一下就舒畅了。是不是想试试?我们下面看看lambda的语法。

lambda表达式语法

(parameters) -> {
  body
}

lambda表达式示例

//定义一个函数,返回两个整数的和
(Integer i1,Integer i2) -> { return i1 + i2; }

//没有参数的一个函数,打印一句话
()->{System.out.println("Hello")}

//返回一个整数常量100
() -> {return 100;}

//打印传入字符串
(String s) -> { System.out.println(s); }

上面的一些示例是语法的标准写法,其实可以支持一些简化的技巧:

  1. 参数的类型既可以明确声明,也可以根据上下文来推断,大部分情况都不需要你声明类型,除非你需要
  2. 空圆括号代表参数集为空
  3. 如果只有一个参数,圆括号也可以省略。 a -> return 2*a
  4. 如果函数主体只有一句,大括号也可以省略,分号也可以省略
  5. reuturn 关键字也可以省约,匿名函数以代码段最后表达的值为函数返回值
  6. 匿名函数返回值类型和代码块表达式一致,当然也可以没有返回值

我们可以看看简化后的代码样子:

//定义一个函数,返回两个整数的和
//参考上述简化技巧1,4,5条
(i1,i2) -> {i1 + i2}

//没有参数的一个函数,打印一句话
//参考上述简化技巧 2,4,6条
() -> System.out.println("Hello")

//返回一个整数常量100
//参考上述简化技巧2,4,5,6
() -> 100

//打印传入字符串
//参考上述简化技巧3,4,6
s -> System.out.println(s)

lambda表达式可以引用函数或类范围内的变量,但不能做修改,你可以理解为lambda应用的都是final变量,当然你可以在匿名函数内部定义局部变量。

public static void doSomeThing(String s, int i) {
  Runnable r = () -> {
    System.out.println(s);
    System.out.println(i);

    //i ++; 不能修改
  };
  new Thread(r).start();
}

函数式接口

lambda对所有函数式接口都兼容。函数式接口就是只有一个抽象方法的接口,比如Runnable,ActionListener,PathFilter等等。java8 也提供了一些函数接口,方便实现函数式编程。

Consumer<Integer>  c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };

可以使用系统定义好的函数式接口实现函数式编程,举个简单例子看看:

public class PredicateTest {

  public static void doFilter(List<Integer> list, Predicate<Integer> p){
    for(Integer i : list){
      if (p.test(i)){
        System.out.println(i);
      }
    }
    //完全java8的写法,使用stream接口可以一句话
    //list.stream().filter(p).forEach(System.out::println);
  }
  public static void main(String[] args) {
      List<Integer> list = Arrays.asList(1, 9, 8, 38, 3, 9, 7, 12, 30);

    //打印出所有偶数
    doFilter(list,i -> i % 2 == 0);
    //打印出所有大于50的数
    doFilter(list,i -> i >= 50);
  }

}

你也可以自己定义函数接口,然后通过lambda表达式使用。我们举一个简单例子:

MyInterface.java

//声明一个函数式接口
@FunctionalInterface
public interface MyInterface {
  public void doSomeThing();
}

FunctionInterfaceTest.java


public class FunctionInterfaceTest {

  public void action(MyInterface handler){
    handler.doSomeThing();
  }
  public static void main(String[] args) {

    FunctionInterfaceTest instance =  new FunctionInterfaceTest();

    //匿名类的方式
    instance.action(new MyInterface(){
      public void doSomeThing(){
        System.out.println("匿名类的方式调用");
      }
    });

      //lambda方式
    instance.action( () -> System.out.println("lambda表达式的方式调用") );
  }

}

lambda表达式的方式实现更简洁,它和匿名类最大的区别式this关键字的解释不同,匿名类的this指向匿名类自身,而lambda表达式引用的this指向外部对象。只是由于两者的编译方式不一样,编译器会将lambda 表达式转化为类里面的私有函数,使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,所有lambda表达式引用的this为外部对象.所有lambda表达式和函数的效果类似。

方法引用

看看下面的一些代码示例的写法:

//System.out::println 等同于 x -> System.out.println(x),对象方法引用
button.setOnAction(System.out::println);

//Integer::compareTo 等同于 (x,y) -> x.compareTo(y)
list.sort(Integer::compareTo);

//构造方法的引用,将流转换成数组
Button[] buttons = stream.toArray(Button[]::new);

通过例子可以看出 ::操作符可以实现下面几种情况的方法引用,同时也支持方法重载,编译器会根据上下文现在相应的函数实现。

object::instanceMethod
Class::staticMethod
Class::instanceMethod
super::instanceMethod

实现原理

lambda表达式其实就是一种语法糖,编译器会将lambda 表达式转化为类里面的私有函数,使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,可以通过观察class字节观察一下。

$ javap  -p  PredicateTest
public class java8_test.PredicateTest {
  public java8_test.PredicateTest();
  public static void doFilter(java.util.List<java.lang.Integer>, java.util.function.Predicate<java.lang.Integer>);
  public static void main(java.lang.String[]);
  private static boolean lambda$0(java.lang.Integer);
  private static boolean lambda$1(java.lang.Integer);
}

可以看到我们定义的PredicateTest 多个两个私有函数 lambda$0 和 lambda$1,如果javap -v -p -s 可以看到更详细的信息,感兴趣可以看看。

总结

对Java8 的lambda表达式的特性评价褒贬不一,有人担心会破坏java代码的严谨和可读性。我觉得只要合理恰当使用会使你的代码更简洁优雅,结合stream api更会产生一样不到的效果。比如你用java8写spark应用你会觉得很爽,也可以写出比较优雅的代码,不用受到scala程序员的鄙视了。

版权声明:原创作品,允许转载,转载时务必以超链接的形式表明出处和作者信息。否则将追究法律责任。来自海汼部落-青牛,http://hainiubl.com/topics/16
本帖由 青牛 于 6年前 解除加精
回复数量: 0
    暂无评论~~
    • 请注意单词拼写,以及中英文排版,参考此页
    • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
    • 支持表情,可用Emoji的自动补全, 在输入的时候只需要 ":" 就可以自动提示了 :metal: :point_right: 表情列表 :star: :sparkles:
    • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif,教程
    • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
    Ctrl+Enter