共计 5437 个字符,预计需要花费 14 分钟才能阅读完成。
前言
在上一篇 Lambda 的讲解中我们就提到过函数式接口,比如:Consumer<String>
consumer = (s) -> System.out.println(s); 其中 Consumer 就是一个函数式接口。这里是通过 Lambda 表达式创建了一个函数式接口的对象。如果不知道什么是 Lambda,请看《神秘的 Lambda》。
函数式接口是什么?
有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambda 就是 Java 中函数式编程的体现,可以使用 Lambda 表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法,这样 Lambda 才能顺利的进行推导。
@FunctionalInterface 注解
与 @Override 注解的作用类似,Java 8 中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上, 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。但是这个注解不是必须的,只要符合函数式接口的定义,那么这个接口就是函数式接口。
static 方法和 default 方法
实在不知道该在哪介绍这两个方法了,所以就穿插在这里了。
static 方法:
java8 中为接口新增了一项功能,定义一个或者多个静态方法。用法和普通的 static 方法一样, 例如:
public interface Interface {
/**
* 静态方法
*/
static void staticMethod() {System.out.println("static method");
}
}
注意: 实现接口的类或者子接口不会继承接口中的静态方法。
default 方法:
java8 在接口中新增 default 方法,是为了在现有的类库中中新增功能而不影响他们的实现类,试想一下,如果不增加默认实现的话,接口的所有实现类都要实现一遍这个方法,这会出现兼容性问题,如果定义了默认实现的话,那么实现类直接调用就可以了,并不需要实现这个方法。default 方法怎么定义?
public interface Interface {
/**
* default 方法
*/
default void print() {System.out.println("hello default");
}
}
注意:如果接口中的默认方法不能满足某个实现类需要,那么实现类可以覆盖默认方法。不用加 default 关键字,
例如:
public class InterfaceImpl implements Interface {
@Override
public void print() {System.out.println("hello default 2");
}
}
在函数式接口的定义中是只允许有一个抽象方法,但是可以有多个 static 方法和 default 方法。
自定义函数式接口
按照下面的格式定义,你也能写出函数式接口:
@FunctionalInterface
修饰符 interface 接口名称 {返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
虽然 @FunctionalInterface 注解不是必须的,但是自定义函数式接口最好还是都加上,一是养成良好的编程习惯,二是防止他人修改,一看到这个注解就知道是函数式接口,避免他人往接口内添加抽象方法造成不必要的麻烦。
FunctionalInterface
public interface MyFunction {void print(String s);
}
看上图是我自定义的一个函数式接口,那么这个接口的作用是什么呢?就是输出一串字符串,属于消费型接口,是模仿 Consumer 接口写的,只不过这个没有使用泛型,而是将参数具体类型化了,不知道 Consumer 没关系,下面会介绍到,其实 java8 中提供了很多常用的函数式接口,Consumer 就是其中之一,一般情况下都不需要自己定义,直接使用就好了。那么怎么使用这个自定义的函数式接口呢?我们可以用函数式接口作为参数,调用时传递 Lambda 表达式。如果一个方法的参数是 Lambda,那么这个参数的类型一定是函数式接口。例如:
public class MyFunctionTest {public static void main(String[] args) {
String text = "试试自定义函数好使不";
printString(text, System.out::print);
}
private static void printString(String text, MyFunction myFunction) {myFunction.print(text);
}
}
执行以后就会输出“试试自定义函数好使不”这句话,如果某天需求变了,我不想输出这句话了,想输出别的,那么直接替换 text 就好了。函数式编程是没有副作用的,最大的好处就是函数的内部是无状态的,既输入确定输出就确定。函数式编程还有更多好玩的套路,这就需要靠大家自己探索了。????
常用函数式接口
Consumer<T>
:消费型接口
抽象方法: void accept(T t),接收一个参数进行消费,但无需返回结果。
使用方式:
Consumer consumer = System.out::println;
consumer.accept("hello function");
默认方法: andThen(Consumer<? super T> after),先消费然后在消费,先执行调用 andThen 接口的 accept 方法,然后在执行 andThen 方法参数 after 中的 accept 方法。
使用方式:
Consumer<String> consumer1 = s -> System.out.print("车名:"+s.split(",")[0]);
Consumer<String> consumer2 = s -> System.out.println("--> 颜色:"+s.split(",")[1]);
String[] strings = {"保时捷, 白色", "法拉利, 红色"};
for (String string : strings) {consumer1.andThen(consumer2).accept(string);
}
输出:
车名:保时捷 –> 颜色:白色
车名:法拉利 –> 颜色:红色
Supplier<T>
: 供给型接口
抽象方法:T get(),无参数,有返回值。
使用方式:
Supplier<String> supplier = () -> "我要变的很有钱";
System.out.println(supplier.get());
最后输出就是“我要变得很有钱”,这类接口适合提供数据的场景。
Function<T,R>
: 函数型接口
抽象方法: R apply(T t),传入一个参数,返回想要的结果。
使用方式:
Function<Integer, Integer> function1 = e -> e * 6;
System.out.println(function1.apply(2));
很简单的一个乘法例子,显然最后输出是 12。
默认方法:
- compose(Function<? super V, ? extends T> before),先执行 compose 方法参数 before 中的 apply 方法,然后将执行结果传递给调用 compose 函数中的 apply 方法在执行。
使用方式:
Function<Integer, Integer> function1 = e -> e * 2;
Function<Integer, Integer> function2 = e -> e * e;
Integer apply2 = function1.compose(function2).apply(3);
System.out.println(apply2);
还是举一个乘法的例子,compose 方法执行流程是先执行 function2 的表达式也就是 3 3=9,然后在将执行结果传给 function1 的表达式也就是 9 2=18,所以最终的结果是 18。
- andThen(Function<? super R, ? extends V> after),先执行调用 andThen 函数的 apply 方法,然后在将执行结果传递给 andThen 方法 after 参数中的 apply 方法在执行。它和 compose 方法整好是相反的执行顺序。
使用方式:
Function<Integer, Integer> function1 = e -> e * 2;
Function<Integer, Integer> function2 = e -> e * e;
Integer apply3 = function1.andThen(function2).apply(3);
System.out.println(apply3);
这里我们和 compose 方法使用一个例子,所以是一模一样的例子,由于方法的不同,执行顺序也就不相同,那么结果是大大不同的。andThen 方法是先执行 function1 表达式,也就是 3 2=6,然后在执行 function2 表达式也就是 6 6=36。结果就是 36。
静态方法:identity(),获取一个输入参数和返回结果相同的 Function 实例。
使用方式:
Function<Integer, Integer> identity = Function.identity();
Integer apply = identity.apply(3);
System.out.println(apply);
平常没有遇到过使用这个方法的场景,总之这个方法的作用就是输入什么返回结果就是什么。
Predicate<T>
:断言型接口
抽象方法: boolean test(T t), 传入一个参数,返回一个布尔值。
使用方式:
Predicate<Integer> predicate = t -> t > 0;
boolean test = predicate.test(1);
System.out.println(test);
当 predicate 函数调用 test 方法的时候,就会执行拿 test 方法的参数进行 t -> t > 0 的条件判断,1 肯定是大于 0 的,最终结果为 true。
默认方法:
- and(Predicate<? super T> other),相当于逻辑运算符中的 &&,当两个 Predicate 函数的返回结果都为 true 时才返回 true。
使用方式:
Predicate<String> predicate1 = s -> s.length() > 0;
Predicate<String> predicate2 = Objects::nonNull;
boolean test = predicate1.and(predicate2).test("&& 测试");
System.out.println(test);
- or(Predicate<? super T> other) , 相当于逻辑运算符中的 ||,当两个 Predicate 函数的返回结果有一个为 true 则返回 true,否则返回 false。
使用方式:
Predicate<String> predicate1 = s -> false;
Predicate<String> predicate2 = Objects::nonNull;
boolean test = predicate1.and(predicate2).test("|| 测试");
System.out.println(test);
- negate(),这个方法的意思就是取反。
使用方式:
Predicate<String> predicate = s -> s.length() > 0;
boolean result = predicate.negate().test("取反");
System.out.println(result);
很明显正常执行 test 方法的话应该为 true,但是调用 negate 方法后就返回为 false 了。
静态方法:isEqual(Object targetRef),对当前操作进行 ”=” 操作, 即取等操作, 可以理解为 A == B。
使用方式:
boolean test1 = Predicate.isEqual("test").test("test");
boolean test2 = Predicate.isEqual("test").test("equal");
System.out.println(test1); //true
System.out.println(test2); //false
其他函数式接口
Bi 类型接口
BiConsumer、BiFunction、BiPrediate 是 Consumer、Function、Predicate 的扩展,可以传入多个参数,没有 BiSupplier 是因为 Supplier 没有入参。
操作基本数据类型的接口
IntConsumer、IntFunction、IntPredicate、IntSupplier、LongConsumer、LongFunction、LongPredicate、LongSupplier、DoubleConsumer、DoubleFunction、DoublePredicate、DoubleSupplier。
其实常用的函数式接口就那四大接口 Consumer、Function、Prediate、Supplier,其他的函数式接口就不一一列举了,有兴趣的可以去 java.util.function 这个包下详细的看。
大家看后辛苦点个赞点个关注哦!后续还会后更多的博客。如有错误,烦请指正。