一. lambda表达式的使用:
在 c/c++ 中可以使用函数指针的形式,把一个函数(一段代码块)传给另一个函数。而在 c# 中可以使用委托的形式也可以实现
delegate int methodName (int a);
public int sum(int a){
return a + 4;
}
// 只要返回值 和 参数类型一致那么就能把方法委托给methodName
methodName=sum;
/* 只要这个方法的返回值和委托返回值一样,就可以使用委托 */
public void print(int x){
print(x);
}
//使用委托
main(){
//把方法传入进去了。
print(methodName(4)); // 4 + 4 = 8;
}
这种把方法(行为)传入另一个方法的形式,可以使得程序在功能的实现更加简单,因此在 java8 中也引入了一种方法来支持。
在 java 中使用的是函数式编程接口的方式来实现的:
// 一个函数式编程接口
@FunctionalInterface
public interface Comp<User> {
boolean test(User user);
}
// 一个方法 返回值 和 参数类型 都和上面接口中的方法一样。
public boolean compUser(User user){
return user.age > 18
}
// 使用函数式接口
public void isUser(User user,Comp<User> comp){
if(comp.test(user)){
System.out.print("zzzzzz");
}
}
main(){
User user = new User(27);
isUser(user, 这里怎么填呢?); //问题出现了,这里怎么填呢?
}
先忽略那个怎么填的问题?来说一下上面代码的用处。
首先编写一个 函数式编程接口 这个接口有一个特点,接口中只能有一个抽象方法,且加了一个 @FunctionalInterface 注解,实际上,任何一个只有一个抽象方法的接口,没加这个注解,也默认加了。
这个注解中有一个方法:
boolean test(User user);
然后,自己写一个方法
// 一个方法 返回值 和 参数类型 都和上面接口中的方法一样。
public boolean compUser(User user){
return user.age > 18
}
到这里,都和c# 的实现差不多;但出现了一个问题
c# 中可以用 :
// 只要返回值 和 参数类型一致那么就能把方法委托给methodName,methodName变成一个方法引用
methodName=sum;
//得到委托,然后把调用的时候只要调用的方法的参数值和委托的返回值一样,那么就可以把委托传进去。
而 java 有着 万物皆是对象 的美称,因此它的处理方式是,只要调用的方法的参数值是一个 函数式接口 ,那么就能把和接口实现方法的 返回值 和 参数值 一样的方法传入,如上面的 compUser(User user)。
回到上面如何传值的问题上来,怎么把这个方法传进来呢?java中并没有委托,但是有两种其他的方式:
- 匿名函数,Lambda 表达式
- 方法引用
1.1 Lambda 匿名函数
匿名函数的写法就是:
(参数列表) -> { 方法体 }
所以
public boolean compUser(User user){
return user.age > 18
}
等于
(User user) -> { return user.age > 18 }
具体关于Lambda表达式的详细,可以自行百度。
那么我们来回答怎么填的问题:
main(){
User user = new User(27);
isUser(user, (User user) -> { return user.age > 18 });
}
1.2 方法引用
在使用Lambda表达式中,虽然大多时候,为了简单,自己写一个lambda表达式如:
e -> e.getName(); // 传入e,获取e的名字
但很多时候,需要使用的方法,已经存在某个类中了,那其实就不用再自己写lambda表达式了,通过方法引用 :: 来把那个方法包装成函数式接口的形式,作为参数传递。
java中有这4种写法:
类型 | 示例 | ||
---|---|---|---|
引用静态方法 | ContainingClass::staticMethodName | String::valueOf | |
引用某个对象的实例方法 | containingObject::instanceMethodName | str::toString | |
引用某个类型的任意对象的实例方法 | ContainingType::methodName | String::length | |
引用构造方法 | ClassName::new | String::new |
下面来通过一个类来详细说一下这4种的用途:
public class User {
private Integer id;
private String name;
public static String getName3(User user){
return user.getName();
}
public String getName2(User user){
return user.getName();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 引用静态方法:
ClassName::staticMethodName
list.Stream.map(User::getName3) //这相当于class.staticMethod 很好理解。
- 引用实例方法
instance::methodName
list.Stream.map(user::getName2) // 这相当于 instance.methodName ,也相当于 user -> user.getName2
- 通过Class调用非静态方法
ClassName:: methodName
/*
这种最不好理解了,怎么可以通过类来调用非静态方法呢?
看前面的代码User 中的 getName 其中geName是没有参数的。
但map(T accept(R r)) 其中函数式接口是有参数的。
这就是 类名::实例方法名 这种方法引用的特殊之处了:
当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。
即:类作为调用方,会作为一个实际特殊的参数。
*/
list.Stream.map(User::getName)
list.stream.map(User::getName2) //这种写法就是错误的。因为getName2自带参数。
public String getName(){
return this.name;
}
- 类名:: new
这个就没什么好说的啦
重点关注: 类名: : 实例方法, 会把调用方作为参数传入。
1.3 java 中的4种函数接口
/**
* Java8内置的四大核心函数式接口
* <p>
* Consumer<T>: 消费型接口
* void accept(T t);
*
* Supplier<T>:供给型接口
* T get();
*
* Function<T, R>: 函数型接口
* R apply(T t);
*
* Predicate<T>: 断言型接口:
* boolean test(T t);
*
*Java8中还提供了其他函数式接口
*/
java中由于泛型的的存在,因此java中内置了4种通用的函数接口,来满足大部分方法。