1. 定义
Java中有且仅有一个抽象方法的接口称为函数式接口,它主要应用于函数式编程的场景中。Java中函数式编程具体的体现 就是Lambda表达式的使用,所以函数式接口可以适用于Lambda使用的接口。
- 只有确保接口中有且仅有一个抽象方法,接口才能被称为函数式接口
- 当然接口中可以有除抽象方法外其他的方法,如默认方法、静态方法和私有方法等
- 通常为保证函数式接口的正确性,通常用
@FunctionalInterface注解来检测所编写的接口是否符合函数式接口的要求:
- 符合,编译成功
- 不符合,则编译失败,说明此时接口中没有抽象方法或者抽象方法的个数多于1
函数式接口格式:
修饰符 interface 接口名{public abstract 返回值类型 方法名(参数列表){};}
如一个简单的函数式接口可定义为:
@FunctionalInterfacepublic interface FuncInterface {public abstract void method();}
2. 使用
函数式接口一般作为方法的参数和返回值类型。例如在之前的浅析Java和Python中Lambda表达式
中其实已经使用过函数式编程,在文中的例子中函数式接口作为方法的参数使用,下面我们再单独回顾下它作为方法参数的使用:函数式接口本质上仍然是接口,因此函数式接口的使用有三种方式:
定义接口的实现类并重写接口中的抽象方法,通过新建实现类的对象使用
public class FuncInterfaceImpl implements FuncInterface{@Overridepublic void method() {System.out.println("Functional interface...");}}
不显式的创建接口的实现类,而是通过匿名内部类的方式直接新建对象使用
- 通过函数式接口的的独有方法-Lambda表达式使用
public class FuncMain {public static void main(String[] args) {// 调用接口实现类的重写方法show(new FuncInterfaceImpl());// 使用匿名内部类重写方法show(new FuncInterface() {@Overridepublic void method() {System.out.println("Functional interface...");}});// 使用Lambda表达式重写方法show(() -> {System.out.println("Functional interface...");});show(() -> System.out.println("Functional interface..."));}public static void show(FuncInterface fi){fi.method();}}
下面看一个使用函数式接口作为返回值类型的例子。ArrayList中的sort()方法需要接收一个比较器,即Comparator,那么我们就将创建的Comparator作为返回值传入sort(),然后再对ArrayList中的元素进行排序。
import java.util.ArrayList;import java.util.Comparator;public class RunnableDemo {public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("Forlogen");list.add("ada");list.add("kobe");System.out.println(list.toString());list.sort(getComparator());System.out.println(list.toString());}public static Comparator<String> getComparator(){return (o1, o2) -> o2.length() - o1.length();}}
2. 特点
Lambda表达式除了可以简化代码的书写外,它还有一个重要的特点:延迟执行。怎么理解延迟执行呢?假设现在有一个日志输出的demo,只有日志等级等于1时才输出传入的日志信息,如果不使用Lambda表达式,代码编写如下:
public class LoggerDemo {public static void main(String[] args) {String m1 = "hello";String m2 = " world";String m3 = " ...";show(1, m1 + m2 + m3);}public static void show(int level, String message){if (level == 1){System.out.println(message);}}}
这样做有个问题:不管最后控制台是否会输出传入的日志信息,字符串的拼接工作都会执行,这样就造成了性能的浪费。而如果使用Lambda表达式作为参数传递,它仅仅是把参数传递到方法中,只有满足条件时才调用接口中的方法,然后进行字符串的拼接。如果条件不满足,接口中的方法就不会执行,那么自然不会进行字符串拼接,也就不会存在性能的浪费发生。
@FunctionalInterfacepublic interface MessageBuilder {public abstract String building();}public class LambdaLoggerDemo {public static void main(String[] args) {String m1 = "hello";String m2 = " world";String m3 = " ...";showLog(1, () ->{return m1 + m2 + m3;});// showLog(2, () ->{return m1 + m2 + m3;});}public static void showLog(int level, MessageBuilder mb){if (level == 1){System.out.println(mb.building());}}}
4. Java中的函数式接口
除了可以根据规则自定义和使用函数式接口外,Java的java.util.function包中提供了大量已编写好的函数式接口供用户使用。
4.1 Supplier
@FunctionalInterfacepublic interface Supplier<T> {/*** Gets a result.** @return a result*/T get();}
java.util.function.SupplierT get(),它用来获取一个泛型参数指定类型的对象数据。因此,Supplier接口又被称为生产者接口,通过指定接口中泛型的具体类型,接口就会生产对应类型的数据。
import java.util.function.Supplier;public class SomeFncInterfacesInJDK {public static void main(String[] args) {String s = getString(()-> "Forlogen");System.out.println(s); // Forlogen}public static String getString(Supplier<String> sup){return sup.get();}}
如上所示,Java程序使用Supplier接口获取一个String类型的数据。因为我们可以在Lambda表达式中编写自己的逻辑,只要最后是获取某一具体类型的数据,中间过程可以做任何的操作。例如,使用Supplier接口实现获取数组中最大值的操作:
import java.util.function.Supplier;public class SomeFncInterfacesInJDK {public static void main(String[] args) {int[] array = {1, 3, 10, 5, 8};// 自动拆箱int max = getMax(() -> {int maxN = array[0];for (int i = 1; i < array.length; i++) {if (array[i] > maxN) {maxN = array[i];}}return maxN;});System.out.println(max); // 10}public static Integer getMax(Supplier<Integer> sup){return sup.get();}}
4.2 Consumer
@FunctionalInterfacepublic interface Consumer<T> {void accept(T t);default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}}
java.util.function.Consumervoid accept(T t),用来消费一个指定类型的数据。虽然接口用来消费某一类型的数据,但具体怎么消费或者说如何使用数据的逻辑由用户自定义。例如,我们可以使用Consumer接口消费一个字符串,消费过程是将字符串中的字母全部转为大写形式。
import java.util.function.Consumer;
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
Consume("Forlogen", (name) -> System.out.println(name.toUpperCase())); //FORLOGEN
}
public static void Consume(String s, Consumer<String> c){
c.accept(s);
}
}
如果我们的消费过程涉及到多步操作呢?即如果需要对每次消费的结果再次进行消费呢?按照一般的写法,我们要定义多个Consumer接口,然后分别调用accept()进行消费。除此之外,Consumer接口中提供了一个默认方法addThen()用于将多个接口组合在一起,然后再对数据进行消费。以String类型的数据为例,方法的使用格式为:
String str = " ...";
Consumer<String> c1, Consumer<String> c2, COnsumer<String> c3; // 可以是多个
c1.addThen(c2).andThen(c3).accept(s)
- 理论上可以使用
addThen()组合无限多个Consumer接口使用,只需要不断地·andThen()即可,最后使用accept()消费数据- 使用中,谁写在前边谁就先消费,后面的接口消费前一个接口的结果
例如我们实现简单的将字符串先转为大写,然后再转为小写,就可以使用addThen()组合两个Consumer接口消费两次。
import java.util.function.Consumer
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
ConsumeAddThen("Forlogen", (name)-> System.out.println(name.toUpperCase()),
(name)->System.out.println(name.toLowerCase()) // FORLOGEN forlogen
);
}
public static void ConsumeAddThen(String s, Consumer<String> c1, Consumer<String> c2){
c1.andThen(c2).accept(s);
}
}
4.3 Predicate
@FunctionalInterface
public interface Predicate<T> {
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);
}
}
java.util.function.Predicateboolean test(T t)用来对指定的数据类型进行判断:
- 如果符合条件,返回true
- 如果不符合条件,返回false
至于条件如何设置由用户自定义。例如,使用Predicate接口判断字符串的长度是否大于5:
import java.util.function.Predicate;
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
boolean b = CheckString("Forlogen", (str) -> str.length() > 5); // true
System.out.println(b);
}
public static boolean CheckString(String s, Predicate<String> p){
return p.test(s);
}
}
类似于逻辑判断中的&& 、||和 !,Predicate接口中提供了默认方法and()、or()和negate()来实现同样的逻辑。
import java.util.function.Predicate;
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
boolean b1 = CheckStringAnd("Forlgoen", (str)-> str.startsWith("a"), (str)->str.length() > 5);
System.out.println(b1); // false
boolean b2 = CheckStringOr("Forlgoen", (str)-> str.startsWith("a"), (str)->str.length() > 5);
System.out.println(b2); // true
boolean b3= CheckStringNegate("Forlgoen", (str)-> str.startsWith("a"));
System.out.println(b3); // true
}
public static boolean CheckStringAnd(String s, Predicate<String> p1, Predicate<String> p2){
return p1.and(p2).test(s);
// 等价于 return p1.test(s) && p2.test(s);
}
public static boolean CheckStringOr(String s, Predicate<String> p1, Predicate<String> p2){
return p1.or(p2).test(s);
// 等价于 return p1.test(s) || p2.test(s)
}
public static boolean CheckStringNegate(String s, Predicate<String> p){
return p.negate().test(s);
// 等价于 return !p.test(s);
}
}
4.4 Function
@FunctionalInterface
public interface Function<T, R> {
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;
}
}
java.util.function.FunctionR apply(T t)用于根据类型T的参数获取类型R的结果,将进行数据类型的转换,如将String类型数据转换为Integer类型:
import java.util.function.Function;
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
int x = convert((str)-> Integer.parseInt(str), "100");
System.out.println(x); // 100
}
public static Integer convert(Function<String, Integer> f, String s){
return f.apply(s);
}
}
Function接口中同样提供了addThen()来执行多步操作。
import java.util.function.Function;
public class SomeFncInterfacesInJDK {
public static void main(String[] args) {
String s1 = convertAndThen((str) -> Integer.parseInt(str) * 3, (num) -> num + " ", "1000");
System.out.println(s1); // 3000
}
public static String convertAndThen(Function<String, Integer> f1, Function<Integer, String> f2, String s){
return f1.andThen(f2).apply(s);
}
}
