java8终于引入了函数式编程,函数式编程即对lambda(λ)表达式的运用,熟练运用λ表达式,我们实现功能的思路会变得更清晰,写起代码来也可以更简捷,下面来认识和学习一下λ
一、认识λ表达式,对于λ表达式,我们看到的使用得最多的就是集合的操作了,下面看几个例子
List<String> list = Arrays.asList("abc", "bcd", "cdef");
List<Integer> lengthList = list.stream().map((item)->item.length()).collect(Collectors.toList());
// 其中(item)->item.length() 为λ表达式
lengthList.forEach((item) -> System.out.println(item));
// 其中(item) -> System.out.println(item) 也为λ表达式
这两个例子中,都是在函数调用的时候,λ表达式作为参数的,一个是stream的map函数,一个是list的forEach函数,我们可以进入函数定义查看一下源码
//map函数
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//forEach函数
default void forEach(Consumer<? super T> action) {
...
}
从形式上我们可以看出来,λ表达式最终被当成一些类型于Function<? super T, ? extends R>,Consumer<? super T>
这样的接口的“对象”来处理的,我们再看这两个接口定义(为了方便,先省去了一些代码)
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
我们发现这两个接口都被@FunctionalInterface注解了,根据FunctionalInterface的注解说明,被它注解的接口,只允许有一个非default的接口方法(未实现的方法),然后我们再回看λ表达式发现,(item)->item.length()为Function的对象,(item) -> System.out.println(item)为Consumer的对象,而观察它们的唯一的非default的方法,也就是(item)->item.length()对应R apply(T t), (item) -> System.out.println(item)对应void accept(T t),是否说明我们可以直接把λ表达式赋值给这些呢,我们可以试试
Function<String, Integer> fun1 = (item) -> item.length();
Consumer<String> fun2 = (item) -> System.out.println(item);
我们发现确实可行。
//因(item) -> item.length()还可以写成
(String item) -> {
int length = item.length();
return length;
}
//函数输入为String类型,输出为int类型,对应于Funcation<String, Integer>的apply方法
//而(item) -> System.out.println(item)也可以写成
(String item) -> {
System.out.println(item);
}
//函数输入为String类型,输出为void类型,对应于Consumer<String>的accept方法
Function可以表示任意只有一个参数,有返回值的函数,类型不限(非void), Consumer可以表示任务只有一个参数,没有返回值的函数,我们可以试一下
public static class FunClass {
public int len(byte[] attr){
return attr.length;
}
public void doNothing(byte[] attr){
}
}
FunClass obj = new FunClass();
Function<byte[], Integer> fun3 = (attr) -> obj.len(attr);
Consumer<byte[]> fun4 = (attr) -> obj.doNothing(attr);
测试发现确实可行,我们还看到java.util.function包下面还有许多的函数接口,许多都是被@FunctionalInterface注解的,它们都是能作为一个λ表达式的引用,因此我们可以认为它们都是函数”模板”
二、函数式编程的写法
还可以有不同的写法,这些都是等价的
Consumer<String> fun5 = System.out::println;
Function<byte[], String> fun6 = String::new;
Function<Integer, String> fun7 = String::valueOf;
Supplier<String> fun8 = new Object()::toString;
Consumer<String> fun5 = (item)-> System.out.println(item);
Function<byte[], String> fun6 = (attr)-> new String(attr);
Function<Integer, String> fun7 = (num) -> String.valueOf(num);
Supplier<String> fun8 = () -> new Object().toString();
Consumer<String> fun5 = (String item)-> {System.out.println(item);};
Function<byte[], String> fun6 = (byte[] attr)-> {return new String(attr);};
Function<Integer, String> fun7 = (Integer num) -> {return String.valueOf(num);};
Supplier<String> fun8 = () -> {return new Object().toString();};
三、函数式编程的用法
Consumer<String> fun5 = System.out::println;
fun5.accept("hello world");
//效果跟System.out.println("hello world");一样
既然可以把一个函数赋值给一个引用,这个引用应该也可以作为函数的参数,确实是这样,比如前面提到的forEach, map函数,我们也可以定义函数
public static <T> void fun(Consumer<T> consumer, T obj){
consumer.accept(obj);
}
//调用方式如下:
fun(System.out::print, "hello world");
fun(new ArrayList<String>()::add, "hello world");
四、自定义函数式编程模板
查看java.util.function包下面的”模板”,我们发现只有一些基础的函数的模板,要是想引用一个有三个或更多参数的函数,现有的模板中似乎就无法满足条件,但是我们可以根据@FunctionalInterface注解来自定义模板
//定义模板
@FunctionalInterface
interface TriFunction<R1, R2, R3, T> {
T apply(R1 r1, R2 r2, R3 r3);
}
//定义类
public static class FunClass {
public static String get(String name, int age, boolean test){
return name + age + test;
}
public static <R1, R2, R3, T> T fun(TriFunction<R1, R2, R3, T> func, R1 r1, R2 r2, R3 r3) {
return func.apply(r1, r2, r3);
}
}
//函数使用
TriFunction<String, Integer, Boolean, String> triFun = FunClass::get;
triFun.apply("word", 12, false);//相当于调用FunClass.get("word", 12, false);
因此我们可以定义任务的类型的函数模板,然后使用它
五、函数模板与函数表达式对应关系并不绝对
Consumer<String> fun10 = String::new;
Function<String, String> fun12 = String::new;
Function<String, Integer> fun13 = (item) -> item.length();
Consumer<String> fun14 = (item) -> item.length();
Function<String, Integer> fun15 = (String item) -> {item.length();};
Consumer<String> fun16 = (String item) -> {item.length();};
Function<String, Integer> fun15 = (String item) -> {return item.length();};
//但是λ表达式涉及到return 的时候,就没法用Consumer了,Consumer<String> fun16 = (String item) -> {return item.length();};这样会编译报错
六、函数模板与λ表达式关系
Function<R, T>
的方法为 T apply(R r);
对应的 λ表达式为 (r) -> {return t};
Consumer 的方法为 void accept(R r);
对应的 λ表达式为 (r) -> { //do work };
当我们定义的模板接口为 T func(R1 r1, R2 r2, R3 r3);的时候
则对应的λ表达式应为 (r1, r2, r3) ->{return t;};
因此我们可以定义任意的函数模板与λ表达式的对应关系