怎么用一行 Lambda 表达式让你的代码更加优雅

1. 介绍一个新朋友 , Lambda 表达式

 

当我们要用 java 代码实现一个计时器 ,要求每秒输出一次当前时间 。我们会写出这样的一份代码

 

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * @author LDX
 * @description 报时器
 **/
public class TimeClick {
    public static void main(String[] args) {
        ActionListener listener = new TimePrinter();

        Timer t = new Timer(1000, listener);
        t.start();

        JOptionPane.showMessageDialog(null, "中止程序 ?");
        System.exit(0);
    }

    static class TimePrinter implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {
            System.out.println("现在的时间是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        }

    }
}

这段代码在功能上没什么问题 ,但是在内容上有那么一点小缺陷 。

 

可以看到 。TimePrinter 这个类只包含的一个 3 行方法 ,我们却要用 7 行的代码将它包装为一个对象 ,再用 1 行代码实例化 ,才能将它传递到主函数中执行 。

 

这是 Java 面向对象思想的体现,但总归不够优雅 ,而在其他的语言中 ,代码段可以直接被处理 ,代码也就更加简洁 ,清晰 。

 

那么 Java 中有没有一种方法或特性可以让 Java 也可以直接处理代码段 ,从而使 Java 代码变得简洁 ,优雅 ,高效呢 ?

 

好消息 ! 好消息 ! Java 支持 Lambda 表达式啦 ,全新特性 ,全新写法 ,帮助你从函数式接口(functional interface) 中解放出来 ,更精简的代码 ,更清晰的思路 ,赶快来尝试一下吧 !

 

lambda 表达式是一个可传递的代码块 ,以计数器为例 ,它长这样 :

 

Timer t = new Timer(1000, event -> {
            System.out.println("现在的时间是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        });

 

完整的程序为 :

 

import javax.swing.*;
import java.awt.*;
import java.util.Date;

/**
 * @author LDX
 * @description 报时器
 **/
public class TimeClick {
    public static void main(String[] args) {

        // 第一个参数为毫秒 ,定义调用时间间隔
        Timer t = new Timer(1000, event -> {
            System.out.println("现在的时间是 : " + new Date());
            Toolkit.getDefaultToolkit().beep();
        });

        // 启动定时器
        t.start();

        // 显示一个中止对话框 ,第一个参数为显示位置
        JOptionPane.showMessageDialog(null, "中止程序 ?");
        System.exit(0);
    }

}

 

发现了吗 ,lambda 表达式帮助我们省略了函数式接口以及对应的实例化 。

 

同时 ,你还可以看到 ,lanbda 表达式的本质就是一个代码块 ,以及必须传入的代码变量规范 。

 

下面就是一个完整的 lambda 表达式了 ,表达式分为三个部分 。

 

(String first, String second) -> {
    if (first.length() < second.length()) return -1; 
    else if (first.length() > second.length()) return 1; 
    else return 0;
}

 

第一个部分是参数 ,包含在第一个 ( ) 中 ,需要给出参数类型以及参数名称 。

 

第二部分是箭头 ,‘ -> ’ , 连接参数和表达式 。

 

第三个部分是表达式 ,包含在 { } 中 ,可以包含显式的 return语句 。

 

如果可以推导出一个 lambda 表达式的参数类型 ,那么可以忽略其类型 。

 

也就是说 ,lambda 表达式还可以这么写 :

 

(first, second) -> {
    if (first.length() < second.length()) return -1; 
    else if (first.length() > second.length()) return 1; 
    else return 0;
}

 

如果表达式只有一个参数 ,且参数类型可以推导得出 ,那么你甚至可以忽略小括号 ,让 Java 自己去推导 。

 

ActionListener listener = event -> 
    System.out.println("现在的时间是 : " + new Date()"); 
    // Instead of (event) -> . . . or (ActionEvent event) ->

 

输入参数的类型都交给 lambda 自己去猜了 ,那么返回类型自然也不再需要我们自己去定义 。

 

lambda 的返回值类型总是会由上下文推导得出 ,不需要我们去指定 。

 

2. 当我们使用 Lambda 表达式 ,我们应该想到什么

 

首先 ,我们要明白 Lambda 表达式可以用在哪些地方 。

 

Java 中已经有很多封装代码块的接口 , 如 ActionListener 或 Comparator ,lambda 表达式与这些接口是兼容的 。

 

对于只有一个抽象方法的接口 ,我们称之为 函数式接口 (functional interface) 。

 

在需要使用到函数式接口的对象时 ,我们就可以提供一个 lambda 表达式 。

 

所以 ,与其把 lambda 表达式看做一个对象 ,不如认为 lambda 表达式是一个函数 。

 

3. 方法引用

我们之前这样实现了一个 lambda 表达式 :

 

ActionListener listener = event -> 
    System.out.println("现在的时间是 : " + new Date()"); 

这时就有人会说了 ,

 

“这个 lambda 表达式看着还是差那么点意思啊 ,能不能再给力一点呢 ,让这个表达式更加简单 呢”

 

可以 ,这时候我们就要再介绍一位新朋友 ,先看看它长什么样子 :

 

Timer t = new Timer(1000, System.out::println) ;

 

表达式 System.out::println 是一个方法引用(method reference ) , 它等价于 lambda表达式 x 一> System.out.println(x)

 

再看一个例子 :

 

Arrays.sort(strings,String::conpareToIgnoreCase) ;

 

从这些例子可以看出 ,要用 ' :: ' 操作符分隔方法名与对象或类名 。

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

 

在前 2 种情况中 ,方法引用等价于提供方法参数的 lambda 表达式 。

 

前面已经提到 ,System.out::println 等价于 x -> System.out.println(x) 。类似地 , Math::pow 等价于 (x,y) -> Math.pow(x, y)

 

对于第 3 种情况 ,第 1 个参数会成为方法的目标 。例如 , String::compareToIgnoreCase 等 同于(x, y) -> x.compareToIgnoreCase(y)

 

如果有多个同名的重栽方法 ,编译器就会尝试从上下文中找出你指的那一个方法 。 例如 ,Math.max 方法有两个版本 ,一个用于整数 ,另一个用于 double 值 。选择哪一个版本取决于 Math::max 转换为哪个函数式接口的方法参数 。类似于 lambda 表达式 ,方法引 用不能独立存在 ,总是会转换为函数式接口的实例 。

 

可以在方法引用中使用 this 参数 。例如 ,this::equals 等同于x -> this.equals(x)

 

使用super 也是合法的 。下面的方法表达式 super::instanceMethod 使用 this 作为目标,会调用给定方法的超类版本。

 

例如

class Greeter {
    public void greet() {
        System.out.println("Hello, world!"); 
    } 
}

class TimedCreeter extends Greeter {
    public void greet {
        Timer t = new Timer(1000, super::greet) ; 
        t.start();
    }
}

如果看到这还不够尽兴 ,可以看看 《Java 核心技术卷1》

你可能感兴趣的:(java)