lambda表达式概述
lambda表达式是一个可传递的代码块,可以在以后执行一次或者多次。
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
package lambda;
import java.util.Arrays;
import java.util.Date;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class LambdaTest {
public static void main(String[] args)
{
String[] plants=new String[] {"Mercury","Venus","Earth","Mar","Juplter","Saturn","Uranus","Nepture"};
System.out.println(Arrays.toString(plants));
System.out.println("Sorted in dictionary order:");
Arrays.sort(plants);
System.out.println(Arrays.toString(plants));
System.out.println("Sorted by length:");
Arrays.sort(plants, (first,second)->first.length()-second.length());
System.out.println(Arrays.toString(plants));
Timer t=new Timer(1000,event->System.out.println("The time is"+ new Date()));
t.start();
JOptionPane.showMessageDialog(null, "Quit Progeam?");
System.exit(0);
}
}
函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
方法引用
在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)
有以下四种形式的方法引用:
类型 | 示例 |
---|---|
引用静态方法 | ContainingClass::staticMethodName |
引用某个对象的实例方法 | containingObject::instanceMethodName |
引用某个类型的任意对象的实例方法 | ContainingType::methodName |
引用构造方法 | ClassName::new |
1、静态方法引用
组成语法格式:ClassName::staticMethodName
注意:
静态方法引用比较容易理解,和静态方法调用相比,只是把 . 换为 ::
在目标类型兼容的任何地方,都可以使用静态方法引用。
例子:
String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)
Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);
2、特定实例对象的方法引用
这种语法与用于静态方法的语法类似,只不过这里使用对象引用而不是类名。实例方法引用又分以下三种类型:
a.实例上的实例方法引用
组成语法格式:instanceReference::methodName
b.超类上的实例方法引用
组成语法格式:super::methodName
方法的名称由methodName指定,通过使用super,可以引用方法的超类版本。例子:
还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);
class Greeter
{
public void greet()
{
System.out.println("Hello World~");
}
}
class TimedGreeter extends Greeter
{
public void greet()
{
Timer t=new Timer(1000,super::greet);
t.start();
}
}
c.类型上的实例方法引用
组成语法格式:ClassName::methodName
注意:
若类型的实例方法是泛型的,就需要在::分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型。
静态方法引用和类型上的实例方法引用拥有一样的语法。编译器会根据实际情况做出决定。一般我们不需要指定方法引用中的参数类型,因为编译器往往可以推导出结果,但如果需要我们也可以显式在::分隔符之前提供参数类型信息。
例子:
String::toString 等价于lambda表达式 (s) -> s.toString()
这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
在泛型类或泛型方法中,也可以使用方法引用。
package com.demo;
public interface MyFunc<T> {
int func(T[] als, T v);
}
3、任意对象(属于同一个类)的实例方法引用
如下示例,这里引用的是字符串数组中任意一个对象的compareToIgnoreCase方法。
String[] stringArray = { “Barbara”, “James”, “Mary”, “John”, “Patricia”, “Robert”, “Michael”, “Linda” };
Arrays.sort(stringArray, String::compareToIgnoreCase);
4、构造方法引用
构造器引用类似于方法引用,只不过方法名为new,又分构造方法引用和数组构造方法引用。
a.构造方法引用(也可以称作构造器引用)
组成语法格式:Class::new
构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字。
例子:
String::new, 等价于lambda表达式 () -> new String()
b.数组构造方法引用
组成语法格式:TypeName[]::new
例子:
int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。等价于lambda表达式 x -> new int[x]。
假想存在一个接收int参数的数组构造方法
IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 创建数组 int[10]
变量作用域
通常,程序需要访问外围方法或者类中的变量,考虑下面这个例子:
public static void repeatMessage(String text,int delay)
{
ActionListener listener=event->
{
System.out.println(text);
Toolkit.getDefaultToolkit().beep();
}
new Timer(delay,listener).start();
}
lambda表达时通常有三个代码块:
1)一个代码块
2)参数
3)自由变量的值,这个指非参数而且不在代码中定义的变量
例如,上面例子中。text就是一个自由变量。
**注释:**关于代码块以及自由变量的值有一个术语:闭包。如果有人吹嘘她们的语言有闭包,现在你也可以自信的说java也有闭包。在java中,lambda就是一个闭包。
lambda表达式中捕获的变量必须实际上是最终变量(effective final)。实际上的最终变量是指,这个变量初始化后就不会再为它赋值。
lambda表达式的体与嵌套块有相同的作用域
处理lambda表达式
使用lambda表达式的重点是延迟执行。毕竟,如果想要立即执行代码,完全可以直接执行,而无需把它包装在一个lambda表达式中。延迟执行的原因是:
在一个单独的线程中运行代码
多次运行代码
在算法的适当位置运行代码
发生某种情况时运行代码
只有在必要时运行代码
例,假设要重复执行一个动作n次。将这个动作和重读次数传递给一个repeat方法:
repeat(n,()->System.out.println("Hello World!"));
要接受这个lambda表达式,需要选择一个函数式接口。此处我们选Runable接口:
public static void repeat(int n,Runable action)
{
for(int i=0;i<n;i++)
action.run();
}
常用的函数式接口如下表:
基本类型的函数式接口如下表: