Java函数式接口FunctionInterface

一、简介

函数式接口是Java中的一个特殊接口类型,它只定义了一个抽象方法。尽管函数式接口中只能有一个抽象方法,但它可以有多个默认方法或静态方法。通过这种接口,Java支持将行为抽象为一个单独的函数,并能够通过lambda表达式或方法引用简洁地表示这些行为。

1.1 定义

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

在上述代码中,MyFunctionalInterface是一个函数式接口,因为它只有一个抽象方法execute()@FunctionalInterface注解不是必须的,但加上这个注解可以显式声明这个接口是函数式接口,并在编译时提供检查机制。

1.2 作用

@FunctionalInterface的作用有以下几点:

  • 编译器检查: 如果一个接口被@FunctionalInterface注解标记,但包含多个抽象方法,那么编译器会报错。
  • 自文档化: 使用@FunctionalInterface注解可以让接口的意图更加明确,表明该接口是为了使用lambda表达式或方法引用设计的。

二、四大核心函数式接口

2.1 Function函数

Function接口是一个功能型接口,它的一个作用就是转换作用,将输入数据转换成另一种形式的输出数据。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    // 对给定的参数应用这个函数,并返回结果。
    R apply(T t);

    // 返回一个组合函数,首先将before函数应用于输入,然后将结果作为参数传递给当前函数。
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    // 返回一个组合函数,首先将当前函数应用于输入,然后将结果作为参数传递给after函数。
    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:接受一个类型的参数,有返回值。功能是将参数赋予相应的方法。
  • compose:先应用beforefunction,再应用实例的function
    实际上:将两个function组合在一起了。先执行before方法,然后将处理的结果传递给当前对象的apply方法。实现了两个function的串联。既然实现了两个function的串联,就能实现多个函数的串联。
  • andThen: 和before函数相反,先应用this function,然后再使用after方法。实现两个方法的串联。
  • identity:传入什么,返回什么。
public class FunctionTest {
    public static void main(String[] args) {
        FunctionTest test = new FunctionTest();
        // 传递行为,而不是传递值
        System.out.println(test.comput(1, value -> 2 * value));
        System.out.println(test.comput(2, value -> 5 + value));
        System.out.println(test.comput(3, Integer::intValue));
        System.out.println(test.convert(4, value -> value + "helloWorld"));
    }

    public int comput(int a, Function<Integer, Integer> function) {
        //apply, 传递的是行为
        int result = function.apply(a);
        return result;
    }

    public String convert(int a, Function<Integer, String> function) {
        return function.apply(a);
    }

    // 对于之前只传递值的写法,几种行为就要定义几种写法。现在可以使用上面的方式去传递行为
    public int method1(int a) {
        return a + 1;
    }

    public int method2(int a) {
        return a * 5;
    }

    public int method3(int a) {
        return a * a;
    }
}

高阶函数:如果一个函数接收一个函数作为参数,或者返回一个函数作为返回值,那么该函数就叫做高阶函数。函数式编程语言js等语言里面都支持大量的高阶函数,JAVA从1.8开始也开始支持高阶函数。

/**
 * compose, andThen方法的使用
 */
public class FunctionTest2 {
    public static void main(String[] args) {
        FunctionTest2 test2 = new FunctionTest2();
        int compute = test2.compute(2, v -> v * 3, v -> v * v);//12
        int compute2 = test2.compute2(2, v -> v * 3, v -> v * v);//36
        System.out.println(compute);
        System.out.println(compute2);
    }

    public int compute(int a, Function<Integer, Integer> function1,
                       Function<Integer, Integer> function2) {
        return function1.compose(function2).apply(a);
    }

    public int compute2(int a, Function<Integer, Integer> function1,
                        Function<Integer, Integer> function2) {
        return function1.andThen(function2).apply(a);
    }
}

2.2 Consumer(消费者)

consumer接口就是一个消费型的接口,并且只要实现一个accept方法,就可以作为一个“消费者”输出信息。

其实,lambda表达式、方法引用的返回值都是Consumer类型,所以,他们能够作为forEach方法的参数,并且输出一个值。

@FunctionalInterface
public 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);
        };
    }
}
  • accept:意味消费一个指定泛型的数据。
public class ConsumerTest {
    public static void test1() {
        System.out.println("**********test1 start*****************");
        List<String> list = Arrays.asList("hello", "world");
        list.forEach(System.out::println);
//        list.forEach(string -> System.out.println(string));
//        list.forEach(new Consumer<String>() {
//            @Override
//            public void accept(String string) {
//                System.out.println(string);
//            }
//        });
    }

    public static void test2() {
        System.out.println("**********test2 start*****************");
        List<String> list = new ArrayList<>();
        Consumer<String> consumer = x -> {
            if (x.startsWith("w")) {
                list.add(x);
            }
        };
        Stream.of("boy", "word", "cat", "way").forEach(consumer);
        list.forEach(System.out::println);
    }

    public static void test3() {
        System.out.println("**********test3 start*****************");
        // 创建一个Consumer实例,打印输入的字符串
        Consumer<String> printer = System.out::println;
        // 使用accept方法来消费(使用)一个值
        printer.accept("hello world!");

        // 另一个使用Consumer的例子,这次是更新对象的某个字段的值
        Consumer<Person> increaseAge = person -> person.setAge(person.getAge() + 1);
        // 创建一个Person对象
        Person person = new Person("张三", 30);
        // 使用Consumer来增加年龄
        increaseAge.accept(person);
        // 打印出增加年龄后的结果
        System.out.println(person.getName() + ": " + person.getAge());
    }

    public static void test4() {
        System.out.println("**********test4 start*****************");
        List<Person> list = new ArrayList<>();
        list.add(new Person("张三", 30));
        list.add(new Person("李四", 26));
        list.add(new Person("王五", 28));

        // 创建一个Consumer实例,添加
        Consumer<Person> consumer = p -> {
            if (p.getName().startsWith("张")) {
                list.add(p);
            }
        };

        consumer = consumer.andThen(
                x -> list.removeIf(y -> y.getName().startsWith("李"))
        );

        list.forEach(System.out::println);
        System.out.println("***************************");

        Stream.of(new Person("张三", 18), new Person("王明", 32)).forEach(consumer);
        list.forEach(System.out::println);
    }

    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private Integer age;
}

2.3 Predicate(谓词)

Predicate接口是一个谓词型接口,其实,这个就是通过实现一个test方法做判断

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    // 在给定的参数上评估这个谓词
    boolean test(T t);

    // 返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    // 返回表示此谓词的逻辑否定的谓词,相当于not
    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);
    }

    // 返回根据Objects.equals(Object, Object)测试两个参数是否相等的谓词
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
  • test:给定一个输入参数,判断是否满足条件。满足则返回true,不满足返回false。
public class PredicateTest {
    public static void main(String[] args) {
        Predicate<Integer> predicate = new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                if (integer > 5) {
                    return true;
                } else {
                    return false;
                }
            }
        };
        System.out.println(predicate.test(6));

        System.out.println("********************");
        predicate = integer -> integer > 5;
        System.out.println(predicate.test(5));
    }
}

2.4 Supplier(供应商)

Supplier接口是一个供给型的接口,其实就是一个容器,可以用来存储数据,然后可以供其他方法使用的这么一个接口。

@FunctionalInterface
public interface Supplier<T> {
    //获取结果值
    T get();
}
  • get方法:获取返回值。
public class SupplierTest {
    public static void test2() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Optional<Integer> first = stream.filter(integer -> integer > 6).findFirst();
        //orElse,如果first中存在数,就返回这个数,如果不存在,就放回传入的数
        System.out.println(first.orElse(777));

        System.out.println("********************");
        Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return new Random().nextInt();
            }
        };
        //orElseGet,如果first中存在数,就返回这个数,如果不存在,就返回supplier返回的值
        System.out.println(first.orElseGet(supplier));
    }

    public static void test() {
        Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return new Random().nextInt();
            }
        };
        System.out.println(supplier.get());

        System.out.println("********************");

        supplier = () -> new Random().nextInt();
        System.out.println(supplier.get());

        System.out.println("********************");
        Supplier<Double> supplier1 = Math::random;
        System.out.println(supplier1.get());
    }

    public static void main(String[] args) {
        test2();
    }
}

2.5 小结

  • Function<T, R> accept:接受一个参数,返回一个结果值
  • Consumer accept:接受一个参数,无返回值
  • Predicate: 接受一个参数,返回一个布尔值
  • Supplier get:无参数,返回一个结果值

三、其他接口

3.1 BiFunction函数

@FunctionalInterface
public interface BiFunction<T, U, R> {

    // 接受两个参数并返回一个结果
    R apply(T t, U u);

    // 返回一个组合函数,首先将当前函数应用于输入,然后将结果作为参数传递给after函数。
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

BiFunction类,双向接口类,提供了两个输入参数,一个输出参数

public class BiFunctionTest {
    public static void main(String[] args) {
        //使用BiFunction来完成
        System.out.println(test2.compute3(1, 2, (value1, value2) -> value1 + value2));
        System.out.println(test2.compute3(1, 2, (value1, value2) -> value1 - value2));
        System.out.println(test2.compute3(1, 2, (value1, value2) -> value1 * value2));
        System.out.println(test2.compute3(1, 2, (value1, value2) -> value1 / value2));

        //使用BiFunction中的andThen.
        System.out.println(test2.compute4(2, 3, (value1, value2) -> value1 + value2,
                value -> value * value));
    }

    //BiFunction类的使用。有两个输入参数
    public int compute3(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
        return biFunction.apply(a, b);
    }

    public int compute4(int a, int b, BiFunction<Integer, Integer, Integer> biFunction,
                        Function<Integer, Integer> function) {
        return biFunction.andThen(function).apply(a, b);
    }
}

为什么BiFunction类中没有Compose方法呢?

倒推一下:因为如果有Compose方法,会先执行参数的Function。无论参数是Function还是BiFunction,返回值也都是一个值,然后就没办法再去执行BiFunction

3.2 Operator(操作员)

除了FunctionConsumerPredicateSupplier这几个基本的函数形式,还有其它派生的函数形式,它们扩展了基本的函数形式,包括UnaryOperator(extends Function)BinaryOperator(extends BiFunction)

  • UnaryOperator是一元操作符的意思,接收一个泛型T对象参数,返回相同T类型对象。
  • BinaryOperator是二元操作符的意思,接收两个相同泛型参数类型T,返回R类型对象。
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

Function接口一样,UnaryOperator会传递一个值并返回一个值。但是,UnaryOperatorFunction之间的区别在于,UnaryOperator必须返回与其传递的对象类型完全相同的对象。
例如,如果将“Person”对象传递给UnaryOperator,则必须将“Person”对象传递回。

public class OperatorTest {
    public static void main(String[] args) {
        UnaryOperator<String> unaryOperator = greet -> greet + " Bob!";
        System.out.println(unaryOperator.apply("Hello")); // Hello Bob!

        UnaryOperator<String> unaryOperator = greet -> greet + " Bob!";
        UnaryOperator<String> unaryOperator1 = greet -> greet + " Jack!";
        String greet = unaryOperator.andThen(unaryOperator1).apply("Hello");
        System.out.println(greet); // Hello Bob! Jack!

        UnaryOperator<String> unaryOperator = greet -> greet + " Bob!";
        UnaryOperator<String> unaryOperator1 = greet -> greet + " Jack!";
        String greet = unaryOperator.compose(unaryOperator1).apply("Hello");
        System.out.println(greet); // Hello Jack! Bob!
    }
}

四、底层实现

@FunctionalInterface注解的实现非常简单,它仅仅是一个标记注解,没有任何属性。然而,编译器在处理标有该注解的接口时,会执行额外的检查,确保接口符合函数式接口的定义。

4.1 编译器检查

在编译时,Java编译器会检查被标记为@FunctionalInterface的接口是否满足以下条件:

  1. 必须有且只有一个抽象方法。
  2. 可以有默认方法或静态方法,但这些方法不会被计入抽象方法的数量中。
  3. 继承的抽象方法不能超过一个。如果接口继承了多个接口,这些接口各自的抽象方法必须能够结合为一个方法。例如,如果一个接口继承了两个接口,这两个接口各自定义了相同签名的抽象方法,那么它们被视为一个方法。

4.2 与Object类方法的关系

函数式接口的抽象方法不能是Object类中的方法,因为Object中的方法在所有类中默认都有实现。如果在函数式接口中只声明Object类的方法(例如toString()),那么这个接口并不会被认为是一个有效的函数式接口。

4.3 lambda表达式使用

定义一个简单的Person类,然后使用lambda表达式解决一些问题

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

public class PersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("zhangsan", 20);
        Person person2 = new Person("lisi", 30);
        Person person3 = new Person("wangwu", 40);
        List<Person> list = Arrays.asList(person1, person2, person3);

        PersonTest test = new PersonTest();
        //测试第一个方法。
        List<Person> list1 = test.getPersonByName("zhangsan", list);
        list1.forEach(person -> System.out.println(person.getName()));
        //测试第二种方法
        List<Person> personByAge = test.getPersonByAge(20, list);
        personByAge.forEach(person -> System.out.println(person.getAge()));
        //测试第三方法
        List<Person> peopleList = test.getPersonByArg(20, list,
                (age, personList) -> personList.stream()
                        .filter(person -> person.getAge() > age).collect(Collectors.toList()));
        peopleList.forEach(person -> System.out.println(person.getName()));
    }

    //使用lambda表达式定义一个 处理的方法
    //filter 方法,参数是一个Predicate 谓语
    //stream 提供了一个将流转换成集合的方法 collect(Collectors.toList())
    public List<Person> getPersonByUsername(String name, List<Person> personList) {
        return personList.stream()
                .filter(person -> person.getName().equals("zhangsan"))
                .collect(Collectors.toList());
    }

    //第二种方式,先直接使用lambda表达式将BiFunction定义好,然后直接将方法的两个参数传入到BiFunction.
    public List<Person> getPersonByAge(int age, List<Person> personList) {
        BiFunction<Integer, List<Person>, List<Person>> biFunction = (ageArg, Persons) -> {
            return Persons.stream().filter(person -> person.getAge() > ageArg).collect(Collectors.toList());
        };
        return biFunction.apply(age, personList);
    }

    //第三种方式,动作也让他自己传递。 函数式接口的好处。
    public List<Person> getPersonByArg(int age, List<Person> personList, BiFunction<Integer, List<Person>,
            List<Person>> biFunction) {
        return biFunction.apply(age, personList);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private Integer age;
}

真谛:函数式接口,传递的是行为,而不是数据。

五、实际应用

在实际开发中,函数式接口广泛应用于Java的标准库中,尤其是在java.util.function包中,提供了一组通用的函数式接口,如Predicate<T>Function<T, R>Supplier<T>等。

5.1 资源清理(自动关闭)

场景:需要在操作完成后自动释放资源(如文件、数据库连接)。

@FunctionalInterface
public interface ResourceHandler<T> {

    void handle(T resource) throws IOException;

    // 封装资源打开和关闭逻辑
    static <T extends AutoCloseable> void useResource(
            Supplier<T> resourceSupplier,
            ResourceHandler<T> handler) throws IOException {
        try (T resource = resourceSupplier.get()) {
            handler.handle(resource);
        }
    }
}

public class ResourceTest {
    public static void main(String[] args) throws IOException {
        // 使用示例:自动关闭文件流
        ResourceHandler.useResource(
                () -> new FileInputStream("data.txt"), // 资源提供者
                inputStream -> {
                    byte[] data = inputStream.readAllBytes();
                    System.out.println("读取到 " + data.length + " 字节");
                } // 处理逻辑(无需手动关闭)
        );
    }
}

优势:将资源获取/释放与业务逻辑解耦,避免忘记关闭资源。

5.2 策略模式(动态算法选择)

场景:根据运行时条件选择不同的处理策略。

@FunctionalInterface
public interface PaymentStrategy {
   void pay(BigDecimal amount);
}

public class PaymentService {
   private PaymentStrategy strategy;

   public void setStrategy(PaymentStrategy strategy) {
      this.strategy = strategy;
   }

   public void executePayment(BigDecimal amount) {
      strategy.pay(amount);
   }
}

public class PaymentTest {
   public static void main(String[] args) {
      // 使用示例:动态切换支付方式
      PaymentService service = new PaymentService();

      // 选择支付宝支付
      service.setStrategy(amount ->
              System.out.println("支付宝支付:" + amount + "元"));
      service.executePayment(new BigDecimal("100.00"));

      // 切换为微信支付
      service.setStrategy(amount ->
              System.out.println("微信支付:" + amount + "元"));
      service.executePayment(new BigDecimal("200.00"));
   }
}

优势:避免继承导致的类膨胀,策略实现更灵活。

5.3 流水线处理(链式操作)

场景:对数据依次进行多个处理步骤(如数据清洗、转换)。

@FunctionalInterface
public interface DataProcessor<T> {

    T process(T data);

    // 组合多个处理器
    default DataProcessor<T> andThen(DataProcessor<T> next) {
        return data -> next.process(this.process(data));
    }
}

public class DataPipelineTest {
    public static void main(String[] args) {
        // 定义处理步骤
        DataProcessor<String> trimProcessor = String::trim;
        DataProcessor<String> upperCaseProcessor = String::toUpperCase;
        DataProcessor<String> appendProcessor = s -> s + "!";

        // 组合流水线
        DataProcessor<String> pipeline = trimProcessor
                .andThen(upperCaseProcessor)
                .andThen(appendProcessor);

        String result = pipeline.process("  hello  "); // 输出 "HELLO!"
    }
}

优势:通过链式调用实现可复用的处理流程。

5.4 异步任务编排

场景:多个异步任务需要顺序执行或合并结果

@FunctionalInterface
public interface AsyncTask<T> {

    CompletableFuture<T> execute();

    // 组合任务:先执行当前任务,再执行后续任务
    default <U> AsyncTask<U> thenCompose(Function<T, AsyncTask<U>> next) {
        return () -> this.execute().thenCompose(
                result -> next.apply(result).execute()
        );
    }
}

public class AsyncTaskTest {
    public static void main(String[] args) {
        // 示例:异步获取用户信息 → 异步获取订单
        AsyncTask<User> fetchUser = () -> userService.getUserAsync(1);
        AsyncTask<Order> fetchOrder = user -> orderService.getOrderAsync(user.getId());

        AsyncTask<Order> combinedTask = fetchUser.thenCompose(fetchOrder);
        combinedTask.execute().thenAccept(order ->
                System.out.println("订单详情:" + order)
        );
    }
}

5.5 条件触发(事件驱动)

场景:满足特定条件时触发操作(如监控阈值报警)。

@FunctionalInterface
public interface ConditionTrigger {
    void triggerIf(Supplier<Boolean> condition, Runnable action);
}

// 实现类:定期检查条件
public class SchedulerTrigger implements ConditionTrigger {

    @Override
    public void triggerIf(Supplier<Boolean> condition, Runnable action) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            if (condition.get()) {
                action.run();
                scheduler.shutdown();
            }
        }, 0, 1, TimeUnit.SECONDS);
    }
}

public class ConditionTest {
    public static void main(String[] args) {
        // 使用示例:CPU使用率超过80%时报警
        SchedulerTrigger trigger = new SchedulerTrigger();
        trigger.triggerIf(
                () -> getCpuUsage() > 80.0, // 条件检测
                () -> sendAlert("CPU过载!")  // 触发动作
        );
    }
}

优势:将条件检测与响应动作解耦,便于复用。

六、总结

@FunctionalInterfaceJava函数式编程的重要组成部分,它通过严格的编译检查和清晰的意图表达,使得函数式接口的使用更加安全和规范。理解其设计理念和底层实现,可以帮助开发者更好地利用lambda表达式和方法引用,从而编写更加简洁、灵活的代码。

七、拓展

7.1 默认方法与静态方法的混淆

很多开发者会错误地认为函数式接口不能有多个方法,这实际上是错误的理解。函数式接口可以有多个默认方法和静态方法,但这些方法不会影响其函数式接口的本质。

7.2 隐式声明

尽管@FunctionalInterface注解可以显式地声明意图,但它并不是必须的。如果没有加这个注解,只要接口符合函数式接口的定义(只有一个抽象方法),它依然是一个有效的函数式接口。

posted @ 2025-04-08 10:04  夏尔_717  阅读(387)  评论(0)    收藏  举报