Java 8我爱编程

Java 8 | Lambda 表达式

2018-04-13  本文已影响22人  懒癌正患者

回顾一下 Java 发展历史中,如果忽视注解(Annotations)、泛型(Generics)等特性,自 Java 语言诞生时起,它的变化并不大。学习了 lambda 表达式后,你可能会感叹 “Java 还能这么写?”。接下来我们来了解一下 lambda 表达式是个什么东东。

什么是lambda 表达式

首先看一段英文引用

Lambda expressions are nameless functions given as constant values, and written exactly in the place where it’s needed, typically as a parameter to some other function.

其中我们需要重点理解一下 written exactly in the place where it’s needed,嗯... 不太好理解。

我们用另外一段英文来解释, they(Lambda Expressions) execute in the context of their appearance。啊... 看完还是一脸懵逼。

我个人认为这句话想表达的意思是,同样的 lambda 表达式,在不同的上下文(context)中执行,得到的结果可能是不同的。我举个例子来说明一下。比如

(one, another) -> one + another

如果接受的参数是数字类型,这个 lambda 表达式的返回结果就是参数之和;如果接受的参数是 String 类型,这个 lambda 表达式的返回结果就是字符串的拼接。返回结果是啥取决于当时的运行上下文。

就算是这样,这有什么了不起吗?为啥这么多人说 lambda 表达式是 Java 8 的重要特性呢?

Java 一直都致力维护其对象至上的特征,在使用过 JavaScript 之类的函数式语言之后,Java 如何强调其面向对象的本质,以及源码层的数据类型如何严格变得更加清晰可感。其实,函数对 Java 而言并不重要,在 Java 的世界里,函数无法独立存在。

在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将其赋值给一个变量,或将他们当做参数传给其他函数。JavaScript 是最典型的函数式编程语言。函数式语言提供了一种强大的功能——闭包,相比于传统的编程方法有很多优势,闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。Java 现在提供的最接近闭包的概念便是 Lambda 表达式,虽然闭包与 Lambda 表达式之间存在显著差别,但至少 Lambda 表达式是闭包很好的替代者。

lambda 表达式的基本语法:

(parameters) -> expression
或者
(parameters) -> { statements; }
欧哲
() -> expression

尝试写一下例子:

(int a, int b) ->    a * b                           // takes two integers and returns their multiplication
(a, b)          ->   a - b                           // takes two numbers and returns their difference
() -> 99                                             // takes no values and returns 99
(String a) -> System.out.println(a)                  // takes a string, prints its value to the console, and returns nothing
a -> 2 * a                                       // takes a number and returns the result of doubling it
c -> { //some complex statements }               // takes a collection and do some procesing

让我们了解一下 Lambda 表达式的规则。

对 lambda 表达式有了简单认识之后,接下来我们介绍一下函数式接口。

什么是函数式接口

在前面的文章中已经介绍过,函数式接口也被称为 Single Abstract Method interfaces (SAM Interfaces),SAM Interfaces 并不是什么新的概念。仅仅意味着接口中只能有有个抽象的方法。只是在 Java 8 中使用 @FunctionalInterface 注解在编译器的层面上强调了这一规则。

比如,Java 8 中 Runnable 接口的定义:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

你可以尝试在函数式接口中添加多个方法,编译器会报错的。

那么函数式接口怎么和 lambda 表达式联系起来的呢?

我们都知道,在 Java 中方法参数都是有类型的,既然我们的 lambda 表达式可以当做参数传递给某个方法,那么 lambda 表达式一定会转换成某种类型当做方法参数。其实这种类型就是函数接口类型。

我们仍然使用例子来理解一下:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(" Hello Java8 !");
    }
}).start();

这段代码启动一个线程,并且让线程打印了 Hello Java8 !

我们使用 lambda 表达式改写一下:

new Thread( () -> log.info("My Runnable")).start();

由于 Runnale 接口是一个函数接口,所以讲 lambda 表达式传递给 Thread 类的构造函数,编译器会尝试将该表达式转换为等效的Runnable代码,如第一段代码中所示。如果编译成功,那么一切运行良好,如果编译器无法将表达式转换为等效的实现代码,它会抱怨。 在这个例子中,lambda 表达式转换为 Runnable 类型。

总结一下,lambda 表达式就是函数接口的一个实例。但是 lambda 表达式自身不包含所实现的函数接口的任何信息,这些信息会在执行上下文中推断出来。

Labmda 表达式的一些例子

  1. 迭代 List
 List<String> pointList = new ArrayList();
        pointList.add("1");
        pointList.add("2");

        pointList.forEach(p -> {
                    log.info(p);
                    //Do more work
                }
        );
  1. 创建线程
new Thread(() -> log.info("My Runnable")).start();
  1. 按名字排序员工
Employee[] employees  = {
                new Employee("David"),
                new Employee("Naveen"),
                new Employee("Alex"),
                new Employee("Richard")};

        log.info("Before Sorting Names: "+ Arrays.toString(employees));
        Arrays.sort(employees, Employee::nameCompare);
        log.info("After Sorting Names "+Arrays.toString(employees));

class Employee {
        String name;

        Employee(String name) {
            this.name = name;
        }

        public static int nameCompare(Employee a1, Employee a2) {
            return a1.name.compareTo(a2.name);
        }

        public String toString() {
            return name;
        }
    }

补充内容

Lambda 表达式与匿名类的区别

使用匿名类与 Lambda 表达式的一大区别在于关键词的使用。对于匿名类,关键词 this 解读为匿名类,而对于 Lambda 表达式,关键词 this 解读为写 Lambda 的外部类。

Lambda 表达式与匿名类的另一不同在于两者的编译方法。Java 编译器编译 Lambda 表达式并将他们转化为类里面的私有函数,它使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,关于 Java 如何将 Lambda 表达式编译为字节码,Tal Weiss 写了一篇很好的文章

Java中的接口Interface是不是继承自Object

简介中提到了在函数式接口中,可以声明和 Object 类中相同的方法而不会报错。于是提出了Java中的接口Interface是不是继承自Object?这么一个问题。

欢迎关注我的微信公众号:java初学者的日常

java初学者的日常
上一篇下一篇

猜你喜欢

热点阅读