背景
匿名内部类
在Java中, 名内部类提供了一种实现class的方式,这个class在应用中可能只会出现一次. 比如, 在Swing或JavaFX应用中,键盘和鼠标事件需要编写大量的事件处理器.与其为每一个事件编写一个独立的事件处理器class,采用下面的写法更常见:
JButton testButton = new JButton("Test Button"); testButton.addActionListener(new ActionListener(){ @Override public void actionPerformed(ActionEvent ae){ System.out.println("Click Detected by Anon Class"); } });
此外, 每个事件都需要一个实现ActionListener的单独类. 每当需要时就创建一个class, 这样的代码更易读. 但它们并不优雅, 因为相当多的代码只为了定义一个方法.
函数式接口
定义ActionListener接口的代码如下:
package java.awt.event; import java.util.EventListener; public interface ActionListener extends EventListener { public void actionPerformed(ActionEvent e); }
ActionListener示例是只有一个方法的接口. 对于Java SE 8, 遵循此模式的接口称为"函数式接口".
> Note: 这种类型的接口, 以前称为单抽象方法类型(SAM).
在Java中使用带有匿名内部类的功能接口是一种常见模式. 除了EventListener类, 类似Runnable和Comparator的接口也以类似的方式使用. 因此, 函数接口可以用于lambda表达式.
Lambda表达式语法
lambda表达式通过将五行代码转换为单个语句来解决匿名内部类的庞大性. 这个简单的水平解决方案解决了内部类提出的"垂直问题".<br> lambda表达式由三部分组成:
参数列表 | 箭头符 | 主体 |
---|---|---|
(int x, int y) | -> | x + y |
主体可以是单个表达式或语句块. 在表达式形式中, 简单地对主体进行求值和返回; 在语句块形式中, 主体的计算方法类似于方法体, return语句将控制权返回给匿名方法的调用者. break和continue关键字在顶层是非法的, 但在循环中是允许的. 如果主体产生一个结果, 每个控制路径必须返回一些东西或抛出异常.<br> 看看这些例子:
(int x, int y) -> x + y() -> 42(String s) -> { System.out.println(s); }
第一个表达式接受两个整数参数, 命名为x和y, 并使用表现形式返回X + Y;<br> 第二个表达式不带参数, 并使用表达式形式返回整数42;<br> 第三个表达式接受一个字符串, 并使用语句块形式将字符串打印到控制台, 并不返回任何内容.
有了语法的基础知识, 让我们看一些例子.
Lambda 示例
Runnable Lambda
这里是如何使用lambdas写一个Runnable:
public class RunnableTest { public static void main(String[] args) { System.out.println("=== RunnableTest ==="); // Anonymous Runnable Runnable r1 = new Runnable(){ @Override public void run(){ System.out.println("Hello world one!"); } }; // Lambda Runnable Runnable r2 = () -> System.out.println("Hello world two!"); // Run em! r1.run(); r2.run(); }}
在这两种情况下, 请注意没有传递参数并返回. Runnable lambda使用语句块形式讲5行代码转换为一个语句.
Comparator Lambda
在Java中, Comparator类用于排序集合. 在以下示例中, 由Person对象组成的ArrayList基于surName进行排序. 以下是Person类中包含的字段.
public class Person { private String givenName; private String surName; private int age; private Gender gender; private String eMail; private String phone; private String address;}
以下代码通过使用一个匿名内部类和几个lambda表达式实现一个Comparator.
public class ComparatorTest { public static void main(String[] args) { ListpersonList = Person.createShortList(); // Sort with Inner Class Collections.sort(personList, new Comparator () { public int compare(Person p1, Person p2) { return p1.getSurName().compareTo(p2.getSurName()); } }); System.out.println("=== Sorted Asc SurName ==="); for (Person p : personList) { p.printName(); } // Use Lambda instead // Print Asc System.out.println("=== Sorted Asc SurName ==="); Collections.sort(personList, (Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName())); for (Person p : personList) { p.printName(); } // Print Desc System.out.println("=== Sorted Desc SurName ==="); Collections.sort(personList, (p1, p2) -> p2.getSurName().compareTo(p1.getSurName())); for (Person p : personList) { p.printName(); } }}
第5-9行很容易被第19行的lambda表达式替换. 请注意, 第一个lambda表达式显示声明了传递给表达式的参数类型. 但是, 从第二个表达式可以看出, 这是可选的. Lambda支持"目标类型", 它从使用它的上下文中推断对象类型. 因为我们将结果分配给用泛型定义的比较器, 所以编译器可以推断这两个参数都是Person类型.
Listener Lambda
最后, 让我们重温一下ActionListener的例子.
public class ListenerTest { public static void main(String[] args) { JButton testButton = new JButton("Test Button"); testButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { System.out.println("Click Detected by Anon Class"); } }); testButton.addActionListener(e -> System.out.println("Click Detected by Lambda Listner")); // Swing stuff JFrame frame = new JFrame("Listener Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(testButton, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); }}
请注意, lambda表达式作为参数传递. 目标类型在许多上下文中使用,包括以下:
* 变量声明 * 分配 * 返回语句 * 数组初始化 * 方法或构造函数参数 * Lambda表达式主体 * 条件表达式 ?: * 强制转型