概述
所谓函数式接口,即接口中有且只有一个抽象方法的接口。在jdk8中用注解“@FunctionalInterface"来声明一个接口是函数式接口。当自己新建接口时,可以不用加这个注解,只要新建的接口满足函数式接口的条件,就会被编译器隐式为函数式接口,为了规范一般建议还是带上注解。有了这个注解,当接口中没有抽象方法或有两个以上的抽象方法时,编译器会报错,可以避免人为失误。
接口方法形式
根据函数式接口的抽象方法的返回值和参数来分,基本可分为:无返回值无参数、无返回值单参数、无返回值多参数、有返回值无参数、有返回值单参数、有返回值多参数等六种形式。根据函数式接口功能来分,基本可分为:工厂型、判断型、消费型、供给型,其实后几种类型都可用工厂型模拟出来。
无返回值无参数
代表Runnable接口。一般主要用于处理业务逻辑,没有输入和输出。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
无返回值单参数
代表Consumer<T>接口。本质是一个消费者,来什么,消费什么,有输入,没输出。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
无返回值多参数
代表BiConsumer<T, U>接口。本质一个更能吃的消费者,多输入,没输出。
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
有返回值无参数
代表Supplier<T>接口。本质是一个生产者,没输入,有输出。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
有返回值单参数
代表Function<T, R>。本质是个加工厂,有输入,有输出。Predicate<T>接口本质产出某种特定物品的加工厂。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
有返回值多参数
代表BiFunction<T, U, R>接口。本质大型加工厂,多输入,有输出。
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
Lambda表达式
Lambda表达式是基于函数式接口来的,相对于之前的匿名内部类这种复杂的写法来说,Lambda表达式是一种极其精简的写法。没有函数式接口,就没法写Lambda表达式。如果把函数式接口比作数学中的一个命题,那么Lambda表达式就是这个命题的精简版的证明过程(匿名内部类写法是一般证明过程)。Lambda表达式是对函数式接口的一种高度概括,一个Lambda表达式整体就代表一个函数式接口的实例对象。抛开函数式接口的Lambda表达式只是一些字符而已,没有任何意义。
举例说明
Function<Integer, Integer> f1 = n->n+1; 这里的n->n+1是Function接口的子类实现类的一个匿名对象实例,这个实例方法的行为是把一个给定数字加1。
如果只有n->n+1,没有前面的函数式接口的申明,那这里的n->n+1将毫无意义可言,就是一堆字符而已。
/**
n->n+1这个整体是Function接口的子类实现类的一个匿名对象,和下面的写法是等价的
new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
return t+1;
}
};
*/
Function<Integer, Integer> f1 = n->n+1;
Function<Integer, Integer> f = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
return t+1;
}
};
形式
根据输入参数多少,输出语句多少,有不同写法,原则就是:单一参数(或无参数的)的小括号可省略;单一语句的(有返回值的单一语句,大括号和return要同时省略)大括号可省略。通用写法如:(a,b,c…)->{…},小括号里是输入参数,大括号里是方法体内容(对于有返回值方法来说,最后要加上return)。下面是具体写法:
情况 | 单语句写法 | 多语句写法 |
---|---|---|
通用情况 | (n1,n2,n3…) -> {…} | (n1,n2,n3…) -> {…} |
无返回值无参数 | () -> … | () -> {…} |
无返回值单参数 | n -> … | n -> {…} |
无返回值多参数 | (n1,n2) -> … | (n1,n2) -> {…} |
有返回值无参数 | () -> r | () -> {…;return r;} |
有返回值单参数 | n -> r | n -> {…;return r;} |
有返回值多参数 | (n1,n2) -> r | (n1,n2) -> {…;return r;} |
具体例子如下代码:
package com.test;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class TestLambda {
public static void main(String[] args) {
//通用写法
Runnable r = ()->{System.out.println("开始线程...");};
//无参,单语句无返回
Runnable r1 = ()->System.out.println("开始线程...");
//无参,多语句无返回
Runnable r2 = ()->{System.out.println("开始线程...");System.out.println("线程执行中...");};
//通用写法
Consumer<Integer> c = n -> {System.out.println(n);};
//单参数,单语句无返回
Consumer<Integer> c1 = n -> System.out.println(n);
//单参数,多语句无返回
Consumer<Integer> c2 = n -> {System.out.println("开始打印中...");System.out.println(n);};
//通用写法
BiConsumer<Integer, Integer> bic = (n1,n2)->{System.out.println(n1+n2);};
//多参数,单语句无返回
BiConsumer<Integer, Integer> bic1 = (n1,n2)->System.out.println(n1+n2);
//多参数,多语句无返回
BiConsumer<Integer, Integer> bic2 = (n1,n2)->{int n =n1+n2;System.out.println(n);};
//通用写法
Supplier<Integer> s = ()->{return 1;};
//无参,单语句返回
Supplier<Integer> s1 = ()->1;
//无参,多语句返回
Supplier<Integer> s2 = ()->{System.out.println("开始获取...");return 1;};
//通用写法
Function<Integer, Integer> f = (n)->{return n+1;};
//单参数,单语句返回
Function<Integer, Integer> f1 = n->n+1;
//单参数,多语句返回
Function<Integer, Integer> f2 = n->{System.out.println("输入了"+n);return n+1;};
//通用写法
BiFunction<Integer, Integer, Integer> bif = (n1,n2)->{return n1+n2;};
//多参数,单语句返回
BiFunction<Integer, Integer, Integer> bif1 = (n1,n2)->n1+n2;
//多参数,多语句返回
BiFunction<Integer, Integer, Integer> bif2 = (n1,n2)->{System.out.println("输入的n1,n2参数分别为:"+n1+","+n2);return n1+n2;};
}
}
用法
按照函数式接口里定义的方法的入参和返回值的实际情况,选择一种合适的表达式形式
package com.test;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class TestUseLambda {
public static void main(String[] args) {
Function<Integer, Integer> f1 = n -> n+1;
Function<Integer, Integer> f2 = n -> 2*n;
Function<Integer, Integer> f3 = n -> 2*n+1;
Function<Integer, Integer> f4 = n -> (n+1)*2;
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("=================n -> 2*n+1=====================");
//对list集合的每个元素先乘以2然后在加1 ,即 n -> 2*n+1
//func1和func11是等价的
func1(list,f1,f2);//可以先定义好一个父类引用
func1(list,n -> n+1,n -> 2*n);//也可现用现写
func11(list,f1,f2);
func11(list,n -> n+1,n -> 2*n);
//两步操作合一,重新定义Function<Integer, Integer>接口的实现
//此时的func3、func1和func11是等价的
func3(list, f3);
func3(list, n -> 2*n+1);
//利用stream的流式处理,一行搞定
list.stream().map(n->2*n+1).forEach(n->System.out.println(n));
System.out.println("=================n ->(n+1)*2=====================");
//对list集合的每个元素先加1然后在乘以2,即n ->(n+1)*2
//func2和func22是等价的
func2(list,f1,f2);
func2(list,n -> n+1,n -> 2*n);
func22(list,f1,f2);
func22(list,n -> n+1,n -> 2*n);
//两步操作合一,重新定义Function<Integer, Integer>接口的实现
//此时的func3、func2和func22是等价的
func3(list, f4);
func3(list, n ->(n+1)*2);
//利用stream的流式处理,一行搞定
list.stream().map(n->(n+1)*2).forEach(n->System.out.println(n));
}
public static void func1(List<Integer> list, Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
for (Integer i : list) {
System.out.print(f1.apply(f2.apply(i))+" ");
}
System.out.println();
}
public static void func11(List<Integer> list, Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
for (Integer i : list) {
System.out.print(f1.compose(f2).apply(i)+" ");//compose可以理解为先执行f2的操作,后执行f1的操作,compose表示意思的是把一个函数的输出作为另一个函数的输入
}
System.out.println();
}
public static void func2(List<Integer> list, Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
for (Integer i : list) {
System.out.print(f2.apply(f1.apply(i))+" ");
}
System.out.println();
}
public static void func22(List<Integer> list, Function<Integer, Integer> f1,Function<Integer, Integer> f2) {
for (Integer i : list) {
System.out.print(f1.andThen(f2).apply(i)+" ");//andThen可以理解为先执行f1,后执行f2,即f1结束后接着f2
}
System.out.println();
}
public static void func3(List<Integer> list, Function<Integer, Integer> f) {
for (Integer i : list) {
System.out.print(f.apply(i)+" ");
}
System.out.println();
}
}
总结
Lambda表达式得名于数学中的 λ 演算。写Lambda表达式,就像练太极,只重其义,不重其招,不要拘泥于形式,要抓住本质。本质就是对方法更深一步的抽象,关注方法的行为(如何输入和输出),忽略方法的形式(方法在哪个类中实现,方法名称,参数列表等)。写代码时,只要明确要输入的是什么,要输出的是什么,就可以写好Lambda表达式了。实在不行,不是还有匿名内部类的写法吗,反正能用表达式的地方都能用匿名内部类。