当我们要用 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 的返回值类型总是会由上下文推导得出 ,不需要我们去指定 。
首先 ,我们要明白 Lambda 表达式可以用在哪些地方 。
Java 中已经有很多封装代码块的接口 , 如 ActionListener 或 Comparator ,lambda 表达式与这些接口是兼容的 。
对于只有一个抽象方法的接口 ,我们称之为 函数式接口 (functional interface) 。
在需要使用到函数式接口的对象时 ,我们就可以提供一个 lambda 表达式 。
所以 ,与其把 lambda 表达式看做一个对象 ,不如认为 lambda 表达式是一个函数 。
我们之前这样实现了一个 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》