Pre
上一节 Java 8 - 01 优雅编程 lambda 以及 @FunctionalInterface注解一点通
中有的时候使用了匿名类来表示不同的行为 ,代码比较啰嗦
代码语言:javascript复制 List targetEngineerList6 = enginnerTest.findEnginner(enginnerList, new EnginnerFilter() {
@Override
public boolean getMatchedEnginner(Enginner enginner) {
return "Python".equals(enginner.getJob());
}
});
Java 8中如何解决这个问题呢?
Lambda表达式 。 它可以让你很简洁地表示一个行为或传递代码。现在你可以把Lambda表达式看作匿名功能,它基本上就是没有声明名称的方法,但和匿名类一样,它也可以作为参数传递给一个方法。
Lambda 初探
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称
- 函数—— Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码。
我们来看下lambda的语法
代码语言:javascript复制 ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
- ( o1, o2) : 参数 ,1个参数的话,可以省略括号
- -> : 箭头,固定写法
- o1.getJob().compareTo(o2.getJob()) : lambda主题
我们看看使用lambda之前的语法
代码语言:javascript复制 Comparator<Enginner> enginnerComparator = new Comparator<Enginner>() {
@Override
public int compare(Enginner o1, Enginner o2) {
return o1.getJob().compareTo(o2.getJob());
}
};
用了Lambda表达式:
代码语言:javascript复制 Comparator<Enginner> enginnerComparator = ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
所以说:
- 参数列表——这里它采用了 Comparator 中 compare 方法的参数,两个 Enginner
- 箭头——箭头 -> 把参数列表与Lambda主体分隔开
- Lambda主体——比较两个 Enginner 的职位。表达式就是Lambda的返回值
为啥这里连return也没有了呢? 如果 你加了 {} , 就必须要带上return了 ( o1, o2) -> { return o1.getJob().compareTo(o2.getJob()) ;}如果没有 { } 就一行代码,其实是可以省略的,lambda中是可以省略的
为了更进一步的理解lambda,我们接着看几个常见的lambda表达式
代码语言:javascript复制 Function<String,Integer> flambda = (String s) -> s.length();
第一个Lambda表达式具有一个 String 类型的参数并返回一个 int 。Lambda没有 return 语句,因为已经隐含了 return 。
这个语句的功能,输入一个字符串,返回字符串的长度 。 如果你需要定义一个Lambda,将输入对象的信息映射 到输出 , java.util.function.Function
接口 是你的不二选择
Predicate<Enginner> predicate= (Enginner e) -> e.getAge() > 30 ;
第二个Lambda 表达式有一个 Enginner类 型的参数并返回一 个 boolean (Enginner 的年龄是否大于30)
在你需要表示一个涉及类型 T 的布尔表达式时,就可以使用java.util.function.Predicate
这个接口
(int x,int y ) -> {
System.out.println(x);
System.out.println(y);
};
第三个Lambda表达式具有两个 int 类型的参数而没有返回值( void 返回)。注意Lambda表达式可以包含多行语句,这里是两行.
代码语言:javascript复制() -> 18
第四个Lambda 表达式没有参数,返回一个 int
代码语言:javascript复制 Comparator<Enginner> comparator2 = ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
第五个Lambda表达式具有两个 Enginner类型的参数,返回一个 int :比较两个 Enginner的职业。
lambda语法
Lambda 的基本语法是
代码语言:javascript复制(parameters) -> expression
或(请注意语句的花括号)
代码语言:javascript复制(parameters) -> { statements; }
小测验一把
根据上述语法规则,以下哪个不是有效的Lambda表达式?
代码语言:javascript复制(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" i;
(5) (String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
(1) 这个Lambda没有参数,并返回 void 。它类似于主体为空的方法: public void run() {} 。
(2) 这个Lambda没有参数,并返回 String 作为表达式。
(3) 这个Lambda没有参数,并返回 String (利用显式返回语句)。
(4) return 是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示: (Integer i) -> {return "Alan" i;}
。 还得有 分号
(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括和分号,如下所示: (String s) -> "Iron Man"
。或者如果你喜欢,可以使用显式返回语句,如下所示: (String s)->{return "IronMan";}
。
再举几个例子 ,加深下印象:
布尔表达式
代码语言:javascript复制 (List<String> list) -> list.isEmpty()
创建对象
代码语言:javascript复制 () -> new Apple(10)
消费一个对象
代码语言:javascript复制(Enginner a) -> {
System.out.println(a.getAge());
}
从一个对象中选择/抽取
代码语言:javascript复制(String s) -> s.length()
组合两个值
代码语言:javascript复制 (int a, int b) -> a * b
比较两个对象
代码语言:javascript复制Comparator<Enginner> comparator2 = ( o1, o2) -> o1.getJob().compareTo(o2.getJob());
函数式接口
代码语言:javascript复制 enginnerTest.findEnginner(enginnerList,engineer -> {
return "Java".equals(engineer.getJob());
});
就可以优化为
代码语言:javascript复制enginnerTest.findEnginner(enginnerList,engineer -> "Java".equals(engineer.getJob()) );
那到底在哪里可以使用Lambda呢?你可以在函数式接口上使用Lambda表达式。在上面的代
码中,我们把 Lambda表达式作为第二个参数传给 filter 方法,因为它这里需要EnginnerFilter
,而这是一个函数式接口.
EnginnerFilter就是一个标准的函数式接口。 因为 EnginnerFilter 仅仅定义了一个抽象方法getMatchedEnginner.
一言以蔽之,函数式接口就是只定义一个抽象方法的接口
实际上 接口现在还可以拥有 默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法抽象方法,它就仍然是一个函数式接口
测试下
下面哪些接口是函数式接口?
代码语言:javascript复制public interface Adder{
int add(int a, int b);
}
public interface SmartAdder extends Adder{
int add(double a, double b);
}
public interface Nothing{
}
- 只有 Adder 是函数式接口。
- SmartAdder 不是函数式接口,因为它定义了两个叫作 add 的抽象方法(其中一个是从 Adder 那里继承来的)。
- Nothing 也不是函数式接口,因为它没有声明抽象方法
用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
当然了,你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。
举个例子
代码语言:javascript复制 // 使用lambda
Runnable r1 = () -> System.out.println("Hello artisan Lambda");
// 使用匿名内部类
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello artisan Inner");
}
};
r1.run();
r2.run();
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。
举个例子
Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。
代码语言:javascript复制(Enginner o1, Enginner o2) -> o1.getJob().compareTo(o2.getJob());
代表接受两个 Enginner 作为参数且返回 int 的函数。
Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法 ,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样
比如 你可以像下面这样直接把一个Lambda传给 process 方法:
代码语言:javascript复制public void process(Runnable r){
r.run();
}
process(() -> System.out.println("This is Great!!"));
不接受参数且返回 void 。 这恰恰是 Runnable 接口中 run 方法的签名。
小测一把
以下哪些是使用Lambda表达式的有效方式?
代码语言:javascript复制(1) execute(() -> {});
public void execute(Runnable r){
r.run();
}
(2) public Callable<String> fetch() {
return () -> "Tricky example ;-)";
}
(3) Predicate<Apple> p = (Apple a) -> a.getWeight();
答案:只有1和2是有效的。
- 第一个例子有效,是因为Lambda
() -> {}
具有签名() -> void
,这和 Runnable 中的抽象方法 run 的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的 - 第二个例子也是有效的。事实上, fetch 方法的返回类型是
Callable
。Callable
基本上就定义了一个方法,签名是() -> String
,其中 T 被 String 代替 了。因为Lambda() -> "Trickyexample;-)"
的签名是() -> String
,所以在这个上下文 中可以使用Lambda。 - 第三个例子无效,因为Lambda表达式
(Apple a) -> a.getWeight()
的签名是(Apple) -> Integer
,这和Predicate:(Apple) -> boolean
中定义的 test 方法的签名不同。