使用匿名类的一个问题是,如果匿名类非常简单,如一个接口仅包含一个方法,那么匿名类可能笨拙和不简洁。这种情况下,你通常将功能作为参数传递到另一个方法,如当有人点击一个按钮应该执行什么样的动作,LE可以让你这么做,将功能作为参数或编码作为数据。
匿名类允许你不用给定类名去实现一个基类,对于只有一个方法的基类,尽管这样通常比以一个有名字的类实现更加简洁,但匿名类看起来有一点过度和笨重,LE让你表达单一方法类的实例更加简洁
1、LE的使用
假如你正在创建一个社交网络程序,为管理员开发一个可以执行任何动作的功能,如发送消息,在程序中满足一定条件的成员,用例描述如下:
名称:在选中的成员上执行一个动作
主要角色:管理员
先决条件:管理员登陆到系统中
后置条件:动作只有在满足一定条件执行
主要成功场景:1、管理员指定条件成员执行某个动作。 2、管理员指定一个动作在这些选中的成员上。 3、管理员选择提交按钮。4、系统查找匹配条件的所有成员。5、系统在所有匹配的成 员上执行指定动作。
扩展:管理员在指定要执行or选择提交动作之前来预览谁符合指定的条件
发生频率:白天多次
假设该社交网络程序的成员由下列person类表示的,社交网络程序的成员都存储在List<Person>实例中
public class Person {
public enum Sex {
MALE, FEMALE
}
String name;
LocalDate birthday;
Sex gender;
String emailAddress;
public int getAge() {
//...
}
public void printPerson() {
//...
}
}
2、创建对成员搜索匹配条件的方法
打印大于指定年龄的成员
public static void printPersonsOlderThan(List<Person> roster, int age) {
for(Person p:roster) {
if(p.getAge() >= age) {
p.printPerson();
}
}
}
该方法可能使的程序脆弱,如引入新的数据类型,程序可能不工作,假如你升级你的应用并且改变了Person类的结构如包含一个不同成员变量;也可能类是记录年龄使用不同的数据类型或算法,你必须重写你许多的API来适应这种改变,此外,这种方法(如果你想打印年龄超过一定年龄)是不必要的限制。比如:
3、创建更加通用的查找方法
该方法比 printPersonsOlderThan更加通用,他打印指定年龄范围内的成员:
public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high) {
for(Person p:roster) {
if(low <= p.getAge() && p.getAge() < high) {
p.printPerson();
}
}
}
若你想打印指定性别的成员,或指定性别和年龄范围的组合?若你改变Person类并添加其他关系属性或地理位置属性?尽管该方法比printPersonsOlderThan更加通用,尝试创建每一个可能搜索查询的方法仍然可以导致脆弱的代码。你可以取代单独的按指定条件单独的代码,在一个不同的类的搜索。
4、内部类中指定搜索条件代码
下列方法打印指定匹配条件的成员:
public static void printPersons(List<Person> roster, CheckPerson tester) {
for(Person p:roster) {
if(tester.test(p)){
p.printPerson();
}
}
}
这方法检查roster中的Person实例,是否满足由tester指定的条件通过tester.test
指定一个搜索条件,你实现CheckPerson接口:
public interface CheckPerson {
boolean test(Person p);
}
下面提供一个特定的实现搜索条件类:
class CheckPersonEligibleForSelectiveService implements CheckPerson {
@Override
public boolean test(Person p) {
return p.gender == Person.Sex.MALE && p.getAge() >= 18
&& p.getAge() <= 25;
}
}
使用该类,你创建它的实例并调用printPersons方法:
printPersons(roster, new CheckPersonEligibleForSelectiveService());
尽管这个方法少了脆弱——若你改变Person的结构你不必重写方法,你仍然有一个条件码:在你的程序中有一个新的接口和内部类为每一个搜索条件执行。因 CheckPersonEligibleForSelectiveService实现一个接口,你可以使用匿名类代替内部类来绕过需要为每一个查询声明一个新类
5、在一个匿名类指定查询条件码
printPersons(roster, new CheckPerson(){
@Override
public boolean test(Person p) {
return p.gender == Person.Sex.MALE && p.getAge() >= 18
&& p.getAge() <= 25;
}
});
这个方法减少代码要求的数量,因为你不必为每一个搜索创建一个新的类,然而,匿名类的语法是笨重的,对于CheckPerson接口只包含一个方法。这种情况下,可以使用LE取代匿名类
6、使用LE指定搜索条件码
CheckPerson是一个函数接口。一个 函数接口是包含仅一个抽象方法的任何接口。(一个函数接口可能包含一个or多个默认方法或static方法)因为一个 函数接口是包含仅一个抽象方法,当你实现它时可以省略该方法的名称,你使用LE取代匿名类表达式:
printPersons(roster, (Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18 && p.getAge() <= 25);
可以在接口CheckPerson处使用标准的函数接口,进一步减少代码数量
7、用LE使用标准函数接口
重新考虑CheckPerson接口
public interface CheckPerson {
boolean test(Person p);
}
这是一个非常简单的接口,它是一个函数接口因为它只包含一个抽象方法。该方法使用一个参数并返回boolean类型的值,这种方法非常简单,在你的应用中定义一个它可能并不值得。因此,JDK中定义一些标准函数接口,在java.util.function下。
例如,你可以使用Predicate<T>在CheckPerson地方,该接口是通用接口的一个例子,泛型指定一个or多个类型参数在<>内,该接口包含仅有一个类型参数T。当用实参声明or实例化一个泛型,你有参数化类型。例如,参数化类型Predicate<Person>如下:
interface Predicate<Person> {
boolean test(Person t);
}
该参数化包含一个与CheckPerson接口中方法相同的参数和返回值的方法,因此,你可以使用Predicate<T>代替CheckPerson接口,如:
public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) {
for(Person p:roster) {
if(tester.test(p)){
p.printPerson();
}
}
}
使用该方法与前面类似:
printPersonsWithPredicate(roster, p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25);
这并不是唯一使用LE的地方,下面方法建议使用LE的其他方法。
8、在你的应用中使用LE
重新考虑 printPersonsWithPredicate方法,哪些地方还可以使用LE
public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) {
for(Person p:roster) {
if(tester.test(p)){
p.printPerson();
}
}
}
该方法检查roster中每个person实例是否满足predicate指定的条件通过tester.test,若Person实例满足指定的条件,Person实例方面printPerson方法被调用。
在这些满足指定条件的Person实例上,你可以指定一个不同的动作去执行,取代调用printPerson方法使用LE。假如你想要LE类似与printPerson方法,带一个Person类型参数和一个返回void,记住,使用LE时,你必须实现一个函数接口,因此,你需要一个函数接口包含一个带一个Person类型参数和返回void抽象方法,Consumer<T>接口包含void accept(T t);具有这些特征。所以,使用Consumer<T>实例取代调用p.printPerson()如下:
public static void processPersons(List<Person> roster, Predicate<Person> tester, Consumer<Person> block) {
for(Person p:roster) {
if(tester.test(p)){
block.accept(p);
}
}
}
使用该方法与前面类似,
processPersons(roster, p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.printPerson());
如果你想使用成员属性,假如你想校验成员属性or遍历它们的联系信息?这种情况下,你需要一个函数接口包含一个返回值。Function<T, R>接口包含R apply(T t);下面的方法通过指定参数mapper遍历数据,并通过参数block执行一个动作:
public static void processPersonsWithFunction(List<Person> roster,
Predicate<Person> tester,
Function<Person, String> mapper,
Consumer<String> block) {
for(Person p:roster) {
if(tester.test(p)){
String data = mapper.apply(p);
block.accept(data);
}
}
}
下面从roster中满足条件的每一个成员遍历邮箱地址,并打印出来:
processPersonsWithFunction(roster, p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email));
9、使用泛型更加通用
重新考虑 processPersonsWithFunction方法,下面接受一个更加通用的版本,一个参数,一个包含任何元素类型的集合,如:
public static<X, Y> void processElements(Iterable<X> source,
Predicate<X> tester,
Function<X, Y> mapper,
Consumer<Y> block) {
for(X p:source) {
if(tester.test(p)){
Y data = mapper.apply(p);
block.accept(data);
}
}
}
使用该方法如:
processElements(roster, p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email));
此方法调用执行下面操作:
a、从source集合中获取一个源对象,这个例子中,从roster集合中获取Person对象
b、过滤对象,匹配Predicate对象tester
c、映射每一个过滤对象当做一个值通过Fucntion对象的mapper
d、执行一个动作在每一个映射对象上通过Consumer对象的block
10、聚合操作使用LE当做参数
下面使用聚合操作打印包含在roster满足条件成员的邮箱地址:
roster.stream()
.filter(p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25)
.map(p -> p.getEmailAddress())
.forEach(email -> System.out.println(email));
Stream<E> steam():获取一个对象源
Stream<T> filter(Predicate<? super T> predicate):匹配prdicate方式过滤对象
<R> Stream<R> map(Function<? super T, ? extends R mapper):通过Function对象映射对象到另一个值
void forEach(Consumer<? super T> action):执行一个动作通过Consumer
filter、map、forEach都是聚合操作,聚合操作处理元素从一个Stream,不直接操作集合。Steam是一个元素的序列,不像一个集合,它不是一个数据结构存储元素,相反,一个Stream从源携带值,如集合,通过管道,一个管道是一个序列的操作流。此外,聚合操作典型的介绍LE作为参数,使你能够定制它们的行为。
LE的语法
a、以逗号分隔的参数列表扩在括号内。在LE中,你可以忽略数据类型。此外,当只有一个参数时,可以省略括号,所以下面也是有效:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
b、箭头记号,->
c、语句体,由简单的表达式or语句块构成,如:
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语句不是表达式;在LE中,你必须将语句块放入括号内({}),然而,你不需要将void方法调用放在{}中,如下面也是合法的LE:
email-> System.out.println(email)
注意:一个LE看起来像一个方法声明,你可以考虑LE作为匿名方法-没有名称的方法,如下多个参数例子:
public class Calculator {
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) {
Calculator myApp = new Calculator();
IntegerMath addition = (a, b) -> a + b;
IntegerMath subtraction = (a, b) -> a - b;
System.out.println("40 + 2 = " +
myApp.operateBinary(40, 2, addition));
System.out.println("20 - 10 = " +
myApp.operateBinary(20, 10, subtraction));
}
}
局部变量访问范围
像内部类和匿名类一样,LE能捕获变量,它们有相同访问内部变量能力。然而,与内部类和匿名类不同的是:LE没有任何遮蔽问题,LE有明确的作用域,不能从父类or引进新层级作用域中继承任何名称。
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
Consumer<Integer> myConsumer = y -> {
System.out.println("x= " + x);
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