java8 中引入了 Lambda 表达式,至于为什么要引入 Lambda 表达式,我想可能是函数式编程太火吧!

java8 新特性 lambda 表达式_java

java作为一门面向对象的编程语言诞生于20世纪90年代。在当时,面向对象编程是软件开发的主流模式。


Java 8可谓是自Java 5以来最具革命性的版本了,她在语言、编译器、类库、开发工具以及Java虚拟机等方面都带来了不少新特性。


由于最近在并发和事件驱动(或者称为“互动”)编程中的优势,函数式编程又逐渐变得重要起来。


这并不意味着面向对象编程不好,相反,最终的趋势是将面向对象编程和函数编程结合起来。


java8 主要在原来面向对象的基础上增加了函数式编程的能力。

为什么要使用lambda表达式

lambda表达式的定义

lambda表达式是一段可以传递的代码,因此它可以被执行一次或者多次。

Lambda表达式可以说是Java 8最大的卖点,她将函数式编程引入了Java。Lambda允许把函数作为一个方法的参数,或者把代码看成数据。

java8 新特性 lambda 表达式_java_02


一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:


Arrays.asList( "p", "k", "u","f", "o", "r","k").forEach( e -> System.out.println( e ) );

实基础语法

在lambda中我们遵循如下的表达式来编写:

expression = (variable) -> action


  • variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;

  • action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。


可以看到Java中lambda表达式的格式:参数、箭头、以及动作实现,当一个动作实现无法用一行代码完成,可以编写 一段代码用{}包裹起来。


lambda表达式可以包含多个参数,例如:

int sum = (x, y) -> x + y;

这时候我们应该思考这段代码不是之前的x和y数字相加,而是创建了一个函数,用来计算两个操作数的和。 后面用int类型进行接收,在lambda中为我们省略去了return。

实战代码对比

之前我们要在另一个独立线程中执行一些逻辑时,通常会将代码放到一个实现Runnable接口的类的run方法中。如下面

public class Worker implements Runnable {    @Override   public void run() {        
       for (int i = 0; i < 1000; i++) {            System.out.println("doWork" + i);        }    } }

执行代码

    public static void main(String[] args) {
        Worker worker = new Worker();        
       new Thread(worker).start();    }

这段代码的关键在于,run方法中包含了你希望在另一个线程中需要执行的代码。

到现在为止,在java 中向其他代码传递一段代码并不是很容易的。你不得不构建一个属于某个类的对象,由它的某个方法来包含所需的代码。

我们看看使用lambda表达式重写上面的方法

        new Thread(() -> {
           for (int i = 0; i < 1000; i++) {               System.out.println("doWork" + i);           }
       }).start();

lambda表达式语法

一般语法

(Type1 param1,Type2 params,...) -> {
   statment1;
   statment2;
   .....    return statmentM;
   
}

单参数语法

可以省略前面的小括号,但是我一般都是带着

param1 ->{    ...   ...   return ...;
}

单语句语法

可以省略后面的大括号

param1 -> statment

函数式接口

在java中有许多已有的接口都需要封装代码块,如Runnable。lambda表达式与这些接口都是向后兼容的。

对于只包含一个抽象方法的接口,你可以通过lambda表达式来创建该接口的对象。这种接口被称为函数式接口。

这里注意一点,大家都知道接口中的方法是抽象的。事实上,接口经常会重新声明Object类中的方法。如toString等 这些方法命名并不是抽象的。

我们看下Runnable的代码 源码

@FunctionalInterfacepublic interface Runnable {    /**
    * When an object implementing interface <code>Runnable</code> is used
    * to create a thread, starting the thread causes the object's
    * <code>run</code> method to be called in that separately executing
    * thread.
    * <p>
    * The general contract of the method <code>run</code> is that it may
    * take any action whatsoever.
    *
    * @see     java.lang.Thread#run()
    */   public abstract void run();
}

注意有个@FunctionalInterface 注解,有2个作用

  • 编译器会检查标注该注解的实体,检查它是否是只包含一个抽象方法的接口。

  • 在jacadoc页面也会包含一条声明,说明这个接口是一个函数式接口。

但是这个注解并不是强制使用,从概念上讲,所有只含有一个抽象方法的接口都是函数式接口,但是使用@FunctionalInterface 注解会让你的代码更加清楚,毕竟代码的可读性是编程的第一规范。

方法引用

有时候,你想要传递给其他代码的操作已经有实现的方法。

Class or instance :: method

比如

(x) ->  System.out.println(x);

就等同于

 System.out::println;

构造器引用

  • 构造器引用同方法引用类似,不同的是在构造器引用中方法名是 new.

  • 对于拥有多个构造器的类,选择使用哪个构造器取决于上下文

Class :: new

变量作用域

外部变量在 lambda 表达式引用时,jdk 8 编译器会隐式做为 final 来处理