匿名类的一个问题是,若是匿名类的实现很是简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来不实用且不清楚。在这些状况下,您一般会尝试将功能做为参数传递给另外一个方法,例如当有人单击按钮时应采起的操做。Lambda表达式使您能够执行此操做,将功能视为方法参数,或将代码视为数据。html
Java教程是为JDK 8编写的。本页描述的示例和实践没有利用后续版本中引入的改进。java
匿名类的一个问题是,若是匿名类的实现很是简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来不实用且不清楚。在这些状况下,您一般会尝试将功能做为参数传递给另外一个方法,例如当有人单击按钮时应采起的操做。Lambda表达式使您能够执行此操做,将功能视为方法参数,或将代码视为数据。算法
上一节“ 匿名类 ”向您展现了如何在不给它命名的状况下实现基类。虽然这一般比命名类更简洁,但对于只有一个方法的类,即便是匿名类也彷佛有点过度和繁琐。Lambda表达式容许您更紧凑地表达单方法类的实例。express
假设您正在建立社交网络应用程序。您但愿建立一项功能,使管理员可以对知足特定条件的社交网络应用程序成员执行任何类型的操做,例如发送消息。下表详细描述了此用例:api
领域 | 描述 |
---|---|
名称 | 对选定的成员执行操做 |
主要演员 | 管理员 |
前提条件 | 管理员已登陆系统。 |
后置条件 | 仅对符合指定条件的成员执行操做。 |
主要成功案例 |
|
扩展 | 1A。管理员能够选择在指定要执行的操做以前或选择“ 提交”按钮以前预览符合指定条件的成员。数组 |
发生频率 | 白天不少次。 |
假设此社交网络应用程序的成员由如下Person
类表示 :网络
公共类人{ public enum Sex { 男,女 } 字符串名称; LocalDate生日; 性别; 字符串emailAddress; public int getAge(){ // ... } public void printPerson(){ // ... } }
假设您的社交网络应用程序的成员存储在一个List<Person>
实例中。方法我介绍一种,其余的能够查看其官网。oracle
一种简单的方法是建立几种方法; 每种方法都会搜索与一个特征匹配的成员,例如性别或年龄。如下方法打印超过指定年龄的成员:app
public static void printPersonsOlderThan(List <Person> roster,int age){ for(Person p:roster){ if(p.getAge()> = age){ p.printPerson(); } } }
注意:A List
是有序的 Collection
。甲集合是一个对象,该组中的多个元素到单个单元中。集合用于存储,检索,操做和传递聚合数据。有关集合的更多信息,请参阅 集合跟踪。ide
这种方法可能会使您的应用程序变得脆弱,这是因为引入了更新(例如更新的数据类型)致使应用程序没法工做的可能性。假设您升级应用程序并更改Person
类的结构,使其包含不一样的成员变量; 也许该类记录和测量年龄与不一样的数据类型或算法。您必须重写大量API以适应此更改。此外,这种方法是没必要要的限制; 例如,若是您想要打印年龄小于某个年龄的成员,该怎么办?
lambda表达式包含如下内容:
括号中用逗号分隔的形式参数列表。该CheckPerson.test
方法包含一个参数, p
表示Person
该类的实例 。
注意:您能够省略lambda表达式中参数的数据类型。此外,若是只有一个参数,则能够省略括号。例如,如下lambda表达式也是有效的:
p - > p.getGender()== Person.Sex.MALE && p.getAge()> = 18 && p.getAge()<= 25
箭头标记, ->
一个主体,由单个表达式或语句块组成。此示例使用如下表达式:
p.getGender()== Person.Sex.MALE && p.getAge()> = 18 && p.getAge()<= 25
若是指定单个表达式,则Java运行时将计算表达式,而后返回其值。或者,您可使用return语句:
p - > { return p.getGender()== Person.Sex.MALE && p.getAge()> = 18 && p.getAge()<= 25; }
return语句不是表达式; 在lambda表达式中,必须用braces({}
)括起语句。可是,您没必要在大括号中包含void方法调用。例如,如下是有效的lambda表达式:
电子邮件 - > System.out.println(电子邮件)
请注意,lambda表达式看起来很像方法声明; 您能够将lambda表达式视为匿名方法 - 没有名称的方法。
如下示例 Calculator
是一个lambda表达式的示例,它采用多个形式参数:
公共类计算器{ interface IntegerMath { int operation(int a,int b); } public int operateBinary(int a,int b,IntegerMath op){ return op.operation(a,b); } public static void main(String ... args){ 计算器myApp = new Calculator(); IntegerMath add =(a,b) - > a + b; IntegerMath减法=(a,b) - > a - b; System.out.println(“40 + 2 =”+ myApp.operateBinary(40,2,另外)); System.out.println(“20 - 10 =”+ myApp.operateBinary(20,10,减法)); } }
该方法operateBinary
对两个整数操做数执行数学运算。操做自己由实例指定IntegerMath
。的例子中定义了lambda表达式两个操做,addition
和subtraction
。该示例打印如下内容:
40 + 2 = 42 20 - 10 = 10
像本地和匿名类同样,lambda表达式能够 捕获变量 ; 它们对封闭范围的局部变量具备相同的访问权限。可是,与本地和匿名类不一样,lambda表达式没有任何阴影问题(有关更多信息,请参阅 阴影)。Lambda表达式是词法范围的。这意味着它们不会从超类型继承任何名称或引入新级别的范围。lambda表达式中的声明与封闭环境中的声明同样被解释。如下示例 LambdaScopeTest
演示了这一点:
import java.util.function.Consumer; 公共类LambdaScopeTest { public int x = 0; class FirstLevel { public int x = 1; void methodInFirstLevel(int x){ //如下语句致使编译器生成 //错误“从lambda表达式引用的局部变量 //必须是最终的或有效的最终“在声明A中: // // x = 99; 消费者<整数> myConsumer =(y) - > { System.out.println(“x =”+ x); //声明A. System.out.println(“y =”+ y); System.out.println(“this.x =”+ this.x); System.out.println(“LambdaScopeTest.this.x =”+ LambdaScopeTest.this.x); }; myConsumer.accept(X); } } public static void main(String ... args){ LambdaScopeTest st = new LambdaScopeTest(); LambdaScopeTest.FirstLevel fl = st.new FirstLevel(); fl.methodInFirstLevel(23); } }
此示例生成如下输出:
x = 23 y = 23 this.x = 1 LambdaScopeTest.this.x = 0
若是在lambda表达式的声明中替换参数x
代替,则编译器会生成错误:y
myConsumer
消费者<整数> myConsumer =(x) - > { // ... }
编译器生成错误“变量x已在方法methodInFirstLevel(int)中定义”,由于lambda表达式不会引入新的做用域级别。所以,您能够直接访问封闭范围的字段,方法和局部变量。例如,lambda表达式直接访问x
方法的参数methodInFirstLevel
。要访问封闭类中的变量,请使用关键字this
。在此示例中,this.x
引用成员变量FirstLevel.x
。
可是,与本地和匿名类同样,lambda表达式只能访问最终或有效最终的封闭块的局部变量和参数。例如,假设您在methodInFirstLevel
定义语句后当即添加如下赋值语句:
void methodInFirstLevel(int x){ x = 99; // ... }
因为这个赋值语句,变量FirstLevel.x
再也不是有效的最终结果。所以,Java编译器生成相似于“从lambda表达式引用的局部变量必须是final或者final final”的错误消息,其中lambda表达式myConsumer
尝试访问FirstLevel.x
变量:
System.out.println(“x =”+ x);
你如何肯定lambda表达式的类型?回想一下lambda表达式,它选择了男性和年龄在18到25岁之间的成员:
p - > p.getGender()== Person.Sex.MALE && p.getAge()> = 18 && p.getAge()<= 25
这个lambda表达式用于如下两种方法:
public static void printPersons(List<Person> roster, CheckPerson tester)
在方法3:在局部类指定搜索条件码
public void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)
在方法6:Lambda表达式使用标准的功能接口
当Java运行时调用该方法时printPersons
,它指望数据类型为CheckPerson
,所以lambda表达式属于此类型。可是,当Java运行时调用该方法时printPersonsWithPredicate
,它指望数据类型为Predicate<Person>
,所以lambda表达式属于此类型。这些方法所指望的数据类型称为目标类型。要肯定lambda表达式的类型,Java编译器将使用发现lambda表达式的上下文或情境的目标类型。所以,您只能在Java编译器能够肯定目标类型的状况下使用lambda表达式:
变量声明
分配
退货声明
数组初始化器
方法或构造函数参数
Lambda表达体
条件表达式, ?:
转换表达式
对于方法参数,Java编译器使用另外两种语言特性肯定目标类型:重载解析和类型参数推断。
考虑如下两个功能接口( java.lang.Runnable
和 java.util.concurrent.Callable<V>
):
public interface Runnable { void run(); } 公共接口Callable <V> { V call(); }
该方法Runnable.run
不返回值,而是返回值Callable<V>.call
。
假设您已invoke
按以下方式重载方法(有关重载方法的详细信息,请参阅 定义方法):
void invoke(Runnable r){ r.run(); } <T> T invoke(Callable <T> c){ return c.call(); }
将在如下语句中调用哪一个方法?
String s = invoke(() - >“done”);
该方法invoke(Callable<T>)
将被调用由于该方法返回的值; 方法 invoke(Runnable)
没有。在这种状况下,lambda表达式的类型() -> "done"
是Callable<T>
。
若是lambda表达式的目标类型及其捕获的参数是可序列化的,则能够 序列化它。可是,与 内部类同样,强烈建议不要对lambda表达式进行序列化。
有四种方法参考:
类 | 例 |
---|---|
参考静态方法 | ContainingClass::staticMethodName |
引用特定对象的实例方法 | containingObject::instanceMethodName |
引用特定类型的任意对象的实例方法 | ContainingType::methodName |
引用构造函数 | ClassName::new |
方法引用Person::compareByAge
是对静态方法的引用。
如下是对特定对象的实例方法的引用示例:
class ComparisonProvider { public int compareByName(Person a,Person b){ return a.getName()。compareTo(b.getName()); } public int compareByAge(Person a,Person b){ return a.getBirthday()。compareTo(b.getBirthday()); } } ComparisonProvider myComparisonProvider = new ComparisonProvider(); Arrays.sort(rosterAsArray,myComparisonProvider :: compareByName);
方法引用myComparisonProvider::compareByName
调用compareByName
做为对象一部分的方法myComparisonProvider
。JRE推断出方法类型参数,在本例中是(Person, Person)
。
如下是对特定类型的任意对象的实例方法的引用示例:
String [] stringArray = {“芭芭拉”,“詹姆斯”,“玛丽”,“约翰”, “Patricia”,“Robert”,“Michael”,“Linda”}; Arrays.sort(stringArray,String :: compareToIgnoreCase);
方法引用的等效lambda表达式String::compareToIgnoreCase
将具备形式参数列表(String a, String b)
,其中a
和b
是用于更好地描述此示例的任意名称。方法引用将调用该方法a.compareToIgnoreCase(b)
。
您可使用名称以与静态方法相同的方式引用构造函数new
。如下方法将元素从一个集合复制到另外一个集合:
public static <T,SOURCE扩展Collection <T>,DEST扩展Collection <T >> DEST transferElements( SOURCE sourceCollection, 供应商<DEST> collectionFactory){ DEST result = collectionFactory.get(); for(T t:sourceCollection){ result.add(T); } 返回结果; }
功能接口Supplier
包含一个get
不带参数并返回对象的方法。所以,您能够transferElements
使用lambda表达式调用该方法,以下所示:
设置<Person> rosterSetLambda = transferElements(roster,() - > {return new HashSet <>();});
您可使用构造函数引用代替lambda表达式,以下所示:
设置<Person> rosterSet = transferElements(roster,HashSet :: new);
Java编译器推断您要建立HashSet
包含类型元素的集合Person
。或者,您能够按以下方式指定:
设置<Person> rosterSet = transferElements(名册,HashSet <Person> :: new);