一、函数式接口
1.1 概念
函数式接口在java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利地进行推导。
备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,java中Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。
1.2 格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
}
由于接口当中抽象方法的public abstract
是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
1.3 @FunctionalInterface注解
- 该注解只能标记在"有且仅有一个抽象方法"的接口上。
- 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionalInterface,那么编译器会报错。
二、函数式编程
2.1 Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
使用 Lambda 表达式的主要原因是:将代码的执行延迟到一个合适的时间点,即调用的时候。
所有的 Lambda 表达式都是延迟执行的。
因为匿名内部类的方法都是要等到调用的时候才会执行。
性能浪费的日志案例
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "world ";
String msgC = "Java ";
log(1,msgA + msgB + msgC);
}
}
//输出: Hello world Java
这种代码存在问题:无论级别是否满足要求,作为log
方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
使用Lambda
使用Lambda必然需要一个函数式接口
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
然后对log方法进行改造
public class Demo03LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "world ";
String msgC = "Java ";
log(2,() ->{
System.out.println("Lambda执行!");//用于证明Lambda的延迟执行
return msgA + msgB + msgC;
});
}
}
// 此时没有任何输出,证明lambda表示式没有执行
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
2.2 使用Lambda作为参数和返回值
三、常用函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function
包中被提供。
3.1 Supplier接口(生产型或供给型接口)
java.util.function.Supplier<T>
接口仅包含一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。 (Supplier
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
3.2 Consumer接口(消费型接口)
java.util.function.Consumer<T>
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
抽象方法:accept
Consumer
接口中包含抽象方法void accept(T t)
,意为消费一个指定泛型的数据。
默认方法:andThen
如果一个方法的参数和返回值类型全都是Consumer
类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer
接口中的default方法andThen
。
public class Demo01ConsumerAndThen {
public static void method(String str, Consumer<String> con1, Consumer<String> con2) {
con1.andThen(con2).accept(str);//con1连接con2,先执行con1消费数据,再执行con2消费数据
}
public static void main(String[] args) {
method("Hello", t -> System.out.println(t.toUpperCase()), (t) -> System.out.println(t.toLowerCase()));
}
}
3.3 Predicate接口(断言型接口)
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
抽象方法:test
Predicate
接口中包含一个抽象方法:boolean test(T t)
。
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate
条件使用"与"逻辑连接起来实现"并且"的效果时,可以使用default方法and
。
默认方法:or
与and
的"与"类似,默认方法or
实现逻辑关系中的"或"。
默认方法:negate
默认方法negate
实现逻辑关系中的"非"(取反)。
3.4 Function接口(转化型接口T->R)
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
抽象方法:apply
Function
接口最主要的抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。
默认方法:andThen
该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen
类似。