函数式接口

定义:有且只有一个抽象方法的接口

可以使用@FunctionalInterface 标识函数式接口

  • 当接口中存在多个抽象方法时,提示:Multiple non-overriding abstract methods found in interface

  • 当接口中不存在抽象方法时,提示:No target method found

  • 函数式接口可以不带泛型,可以有多个用default修饰的默认实现方法

1
2
3
4
5
6
7
8
9
@FunctionalInterface
interface HspFunction<T, R> {
R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果

//函数式接口,依然可以有多个默认实现方法
default public void ok() {
System.out.println("ok");
}
}

传统的方式实现函数式接口,使用匿名内部类的形式。使用Lambda表达式配合函数式接口可以简化代码:不关注单个抽象方法的权限,返回值,签名,只关注于该方法的入参和方法体即可。但是仅限于一个抽象方法的接口的匿名内部类的简化情况。

情况 简化方式
Lambda的入参,以前都要指名类型 其实参数类型也是可以动态推断的,可以不写参数类型
Lambda的入参,只有一个入参的时候,还是要加上 () 可以省略 () 不写,当且仅当一个参数的时候
Lambda的方法体,只有一个语句的时候(空返回值) 可以直接省略方法体的{}
Lambda的方法体,只有一个语句的时候(有返回值) 也可以直接省略方法体的{},甚至省略return关键字

参考文章:JDK8新特性第一篇:Lambda表达式 - 知乎 (zhihu.com)

四大函数式接口

函数式接口 参数类型 对应程序逻辑的抽象 具体场景
Function <出参类型, 入参类型> 程序中映射逻辑的抽象 比如我们写得很多的函数:接收入参,返回出参,方法代码块就是一个映射的具体逻辑。
Predicate <入参类型>,test方法的出参为boolean类型 程序中判断逻辑的抽象 比如各种if判断,对于一个参数进行各种具体逻辑的判定,最后返回一个if else能使用的布尔值
Consumer <入参类型>,apply方法的出参为void 程序中的消费型逻辑的抽象 就比如Collection体系forEach方法,将每一个元素取出,交给Consumer指定的消费逻辑进行消费
Suppiler <出参类型>,get方法的入参为void 程序中的生产逻辑的抽象 就比如最常用的,new对象,这就是一个很经典的生产者逻辑,至于new什么,怎么new,这就是Suppiler中具体逻辑的写法了

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MappingExample {
public static void main(String[] args) {
Integer i1 = show("abcd", null);
Integer i2 = show("abcd", s -> s.length() + 3);
System.out.printf("第1个返回值:%d,第2个返回值:%d", i1, i2);
}

//可以在调用方法时传入个性化的映射逻辑
public static Integer show(String s, Function<String, Integer> function) {
//当传入的方法映射为null时,调用默认逻辑
if (Objects.isNull(function)) {
return s.length();
}
return function.apply(s);
}
}

同时,在 java.util.function 包在上面的基本接口外进行了拓展

扩展模式 说明 案例
泛型参数具体化 相当于具体化了四大函数式接口的泛型,让他们更具体的对于某一种情况进行操作。比如泛型被规定为int long double等等 IntToDoubleFunction
泛型类型多元化 相当于提供了二元操作,比如二元Consumer,就是消费两个参数,二元Function,就是两个入参对应一个出参这种 BiConsumer二元消费者、BiFunction 二元函数
泛型参数统一化 统一,XXXXOperator接口,【主要是Function扩展体系】,出参和入参的类型,强行让他们的类型固定为同一类型 UnaryOperator一元运算符、DoubleBinaryOperator小数型二元运算符

还有很多泛型参数具体化后的各种Consumer和Supplier,以及像 IntToDoubleFunction 之类的拓展;还有泛型参数多元化BiConsumer二元消费者、BiFunction 二元函数;以及泛型参数统一化的XXXXOperator接口,如等

参考资料:

JDK8新特性第二篇:四大函数式接口【Function/Consumer/Supplier/Perdicate】、接口的扩展方法【default/static】 - 知乎 (zhihu.com)

Java函数式接口的一个疑惑:为什么Comparator接口有两个抽象方法compare和equals,Comparator还是一个函数式接口?(@FunctionalInterface)-CSDN博客

方法引用

Lambda表达式将函数简化为入参、方法体、返回值,当这些条件和外部方法匹配时,可以简化为 XXX::XX 的格式

Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack() Thread.currentThread()::dumpStack
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s) System.out::println

静态方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//传统方式:匿名内部类
Comparator<Integer> c1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
};
//使用Lambda表达式,重写上面的方法
Comparator<Integer> c2 = (i, j) -> (i - j);
//使用Lambda表达式,重写的是ToIntFunction的applyAsInt方法
Comparator<Integer> c3 = Comparator.comparingInt(i -> i);
//静态方法引用
Comparator<Integer> c4 = Integer::compare;

构造器引用

1
2
3
4
5
6
7
8
9
10
11
//功能要求:新建实例对象
//使用Lambda表达式
Supplier<User> s1 = () -> new User();
//使用构造器引用
Supplier<User> s2 = User::new;

//功能要求:把一个Set集合转化为List集合
//使用Lambda表达式
Function<Set, List> f1 = set -> new ArrayList(set);
//使用构造器引用
Function<Set, List> f2 = ArrayList::new;

实例对象引用

案例1:字符串分隔并输出

1
2
3
4
5
6
String s = "abc,def,ghi,jkl";
//调用实例对象的方法,有Lambda表达式和方法引用两种形式
Function<String, String[]> func1 = str -> s.split(str);
Function<String, String[]> func2 = s::split;
//输出按","分隔的字符串数组
Arrays.asList(func2.apply(",")).forEach(System.out::println);

案例2:验证数字是否大于某个值

1
2
3
4
5
6
7
8
9
@Test
public void test1() {
//如果不新增方法,也可以用Lambda表达式的 i -> i >50
Predicate<Integer> p = this::testInteger;
}

public boolean testInteger(Integer integer) {
return integer > 50;
}

非静态方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import lombok.Getter;
import lombok.Setter;

import java.util.function.BiConsumer;
import java.util.function.Function;

public class Test {
public static void main(String[] args) {
//无参方法,相当于 user -> user.getUserName();
Function<User, String> f1 = User::getUserName;

//带参方法,相当于 (user, str) -> user.setUserName(str);
BiConsumer<User, String> f2 = User::setUserName;
}
}

@Getter
@Setter
class User{
private String userName;
private String password;
}

总结

类型 格式 说明 案例
静态方法引用 类名::方法名 方法体直接引用另外一个类的静态方法 (args) -> ClassName.staticMethod(agrs)的Lambda,转化为ClassName::staticMethod
构造器引用 类名::new 通过构造器创建实例对象 (args) -> new ClassName(agrs) 可以被简化为 ClassName::new
实例对象方法引用 实例对象名::方法名 调用内存中实例对象所在类的方法 (args) ->object.XXX(args) 可以被简写为:object::XXX【备注:object是一个程序中定义的对象即可,XXX就是该Object的方法】
非静态方法的引用 类名::方法名 调用内存中实例对象所在类的方法 有参的(实例名, args) -> 实例名.XXXXX(agrs) ,无参的 () ->实例名.可以被简化为 实例所在类名::XXXXX

参考资料:JDK8新特性第三篇:方法引用 + StreamAPI - 知乎 (zhihu.com)

复合 Lambda 表达式

比较器复合

Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。

1
2
3
inventory.sort(comparing(Apple::getWeight)//按重量比较
.reversed() // 递减排序
.thenComparing(Apple::getCountry)); // 两个苹果一样重时按照国家排序

谓词复合

谓词接口包括三个方法: negate、 and和or,让你可以重用已有的Predicate来创建更复杂的谓词。比如,可以使用negate方法来返回一个Predicate的非,比如苹果不是红的

1
Predicate<Apple> notRedApple = redApple.negate(); // 产生现有Predicate对象redApple的非

and和or方法是按照在表达式链中的位置,从左向右确定优先级的。因此, a.or(b).and©可以看作(a || b) && c

1
2
// 链接Predicate的方法来构造更复杂Predicate对象
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150).or(a -> "green".equals(a.getColor()));

函数复合

可以把Function接口所代表的Lambda表达式复合起来。 Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。

andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。比如,假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组合成一个函数h,先给数字加1,再给结果乘2:

1
2
3
4
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 结果是4

compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):

1
2
3
4
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // 结果是3

参考资料:【Java 8】Lambda表达式-CSDN博客