一、函数式接口

1.1 概念

函数式接口在 Java 中是指:有且仅有一个抽象方法的接口

1.2 格式

修饰符 interface 接口名称 {
    返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}

例如:

public interface MyFunctionalInterface {
    void myMethod();
}

1.3 @FunctionalInterface 注解

@Override 注解的作用类似,为了保证接口 有且仅有一个抽象方法,Java 8 中专门为其引入了一个新的注解: @FunctionalInterface

@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();
}

注意:即使不使用该注解,只要满足函数式接口的定义也认为该接口是一个函数式接口不影响其使用。

1.4 自定义函数式接口的使用

自定义函数式接口的典型使用场景就是作为方法的参数,例如:

public class DemoFunctionalInterface {
    // 使用自定义的函数式接口作为方法参数
    private static void print(MyFunctionalInterface inter) {
        // 使用自定义的函数式接口方法
        inter.myMethod();
    }

    public static void main(String[] args) {
        // 调用方法
        print(() -> System.out.println("执行 Lambda 函数方法"));
    }
}

二、函数式编程

2.1 Lambda 延迟执行

在讲 Lambda 延迟执行 之前,我们先来看一个日志打印的案例:

public class DemoLogger {

    private static void log(int lever,String msg) {
        if(lever == 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);
    }
}

上面的案例存在一个问题,无论日志级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会先被拼接然后传入方法内。如果级别不满足条件的话,字符串的拼接操作就白做了,存在着性能上的浪费。而 Lambda 表达式是延迟执行的,正好可以作为解决方案,用以提升性能。

@FunctionalInterface
public interface MessageBuilder {
    String builderMessage();
}

public class DemoLogger {

    private static void log(int lever,MessageBuilder builder) {
        if(lever == 1){
            System.out.println(builder.builderMessage());
        }
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";

        log(1,() -> {
            System.out.println("级别为1时执行");
            return msgA+msgB+msgC;
        });

        log(2,() -> {
            System.out.println("级别不为1时执行");
            return msgA+msgB+msgC;
        });
    }

}

运行程序,我们来看下控制台的输出结果:

级别为1时执行
HelloWorldJava

从输出结果上可以看出,如果不符合日志级别要求的话,Lambda 将不会被执行,进而达到节省性能的效果。

2.2 使用 Lambda 作为参数

向上面的日志打印案例实际上就是将 Lambda 当作一个参数来使用,只要方法的参数是一个函数式接口类型,那么就可以使用 Lambda 表达式进行替代。例如 Runnable 接口就是一个函数式接口:

public class DemoParameter {

    private static void startRunnable(Runnable task) {
        new Thread(task).start();
    }

    public static void main(String[] args) {
        startRunnable(() -> System.out.println("线程执行中"));
    }
}

2.3使用 Lambda 作为返回值

同理,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个 Lambda 表达式:

public class DemoResult {
    private static Comparator<Integer> newComparator() {
        return (a, b) -> b - a;
    }

    public static void main(String[] args) {
        Integer[] array = new Integer[]{0, -5, 33, 100, 88, -40};
        // 排序前
        System.out.println(Arrays.toString(array));
        Arrays.sort(array,newComparator());
        // 排序后
        System.out.println(Arrays.toString(array));
    }

}

三、Lambda 四大核心函数式接口

除了我们自己定义函数式接口外,JDK 也提供了大量的常用函数式接口用以丰富 Lambda 的使用场景,它们主要在 java.util.function 包中被提供。

3.1 Supplier 接口

java.util.function.Supplier<T> 接口仅包含一个无参方法: T get()。用来获取一个泛型参数指定类型的对象数据。

public class DemoSupplier {

    private static String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        String msgA = "Hello ";
        String msgB = "World ";
        System.out.println(getString(() -> msgA + msgB));
    }

}

3.2 Consumer 接口

java.util.function.Consumer<T> 接口与 Supplier 接口正好相反,它不是用于生产一个数据,而是消费一个数据,它的数据类型由泛型决定。

Consumer 接口有两个方法:

accept抽象方法用于消费一个数据
andThen默认方法组合消费一个数据

3.2.1 accept 方法

public class DemoConsumer {

    private static void consumerString(Consumer<String> one){
        one.accept("Hello");
    }

    public static void main(String[] args) {
        consumerString((s)->System.out.println(s));
    }

}

3.2.2 andThen 方法

public class DemoConsumer {

    private static void consumerString(Consumer<String> one,Consumer<String> two){
        one.andThen(two).accept("Hello");
    }

    public static void main(String[] args) {
        consumerString(
                (s)->System.out.println(s.toUpperCase()),
                (s)->System.out.println(s.toLowerCase())
        );
    }

}

3.3 Predicate 接口

当我们需要对某种类型的数据进行判断,从而得到一个布尔值类型的结果的时候,可以使用java.util.function.Predicate 接口。

Predicate 接口有如下方法:

test抽象方法用于条件判断
and默认方法组合条件判断,表示 与 关系
or默认方法组合条件判断,表示 或 关系
negate默认方法组合条件判断,表示 非 关系

3.3.1 test 方法

条件判断的条件是根据传入的 Lambda 表达式逻辑 ,下面的示例的判断逻辑是传入的值是否大于0。

public class DemoTest {

    private static void method(Predicate<Integer> predicate) {
        boolean result = predicate.test(5);
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s > 0);
    }

}

3.3.2 and 方法

如果要判断字符串既包含字母 a 又要包含字母 e,那么可以使用 and 方法:

public class DemoAnd {
    private static void method(Predicate<String> one,Predicate<String> two) {
        boolean result = one.and(two).test("abcd");
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s.contains("a"),(s) -> s.contains("e"));
    }

}

3.3.3 or 方法

如果只需要满足一个条件即可,那就可以使用 or 方法:

public class DemoOr {
    private static void method(Predicate<String> one,Predicate<String> two) {
        boolean result = one.or(two).test("abcd");
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s.contains("a"),(s) -> s.contains("e"));
    }

}

3.3.4 negate 方法

剩下的就只有取反的逻辑了,判断值不能大于0:

public class DemoNegate {

    private static void method(Predicate<Integer> predicate) {
        boolean result = predicate.negate().test(5);
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s > 0);
    }

}

3.4 Function 接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

apply(T t)抽象方法根据类型 T 参数获取类型 R 的结果
andThen默认方法用来进行组合操作

3.4.1 apply 方法

使用场景如:将 String 类型转换为 Integer 类型:

public class DemoFunctionApply {

    private static void method(Function<String,Integer> function){
        Integer apply = function.apply("10");
        System.out.println(apply + 0);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s));
    }
}

3.4.2 andThen 方法

public class DemoFunctionAndThen {

    private static void method(Function<String,Integer> one,
                               Function<Integer,Integer> two){
        Integer apply = one.andThen(two).apply("10");
        System.out.println(apply + 0);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s),i -> i *= 10);
    }
}
02-13 21:20