Java进阶之-函数式编程

在Java中,函数式表达式是指可以像普通对象一样传递和使用的代码块。Java 8引入了函数式编程的概念,通过Lambda表达式、方法引用等方式来实现函数式编程。

接下来将从 Lambda 表达式、常见的内置函数式接口、Stream API、Optional类 四个方面去逐一介绍:

1. Lambda表达式

lambda表达式是为了简化匿名内部类的写法,而该匿名内部类又必须是函数式接口。

1)什么是函数式接口?

函数式接口指的是只包含一个抽象方法的接口,如下图的 Runnable 即为 函数式接口。

函数式接口的类上面往往都有一个注解 @FunctionalInterface,该注解表明该接口是一个函数式接口,并且约束该接口只能有一个抽象方法。

2)lambda表达式的写法

lambda表达式可以将匿名内部类的方法简化成 () -> {} 的形式,其中 () 里面为方法的参数,没有参数即为空括号 () ,{} 里面为具体的代码实现逻辑。如下:

简化前:

new Thread(new Runnable() {
   @Override
   public void run() {
       log.info("具体的实现逻辑!");
   }
}).start();

简化后(如果 {} 内只有一行代码的话,大括号 {} 也可以省略。):

new Thread(() -> log.info("具体的实现逻辑!")).start();

2.  常见的内置函数式接口

我们知道函数式接口是指只有一个抽象方法的接口,在 Java 8 中提供了许多内置的函数式接口,如 FunctionConsumerSupplierPredicate等。

接下来我们将分别介绍一下这几种内置函数式接口的用法:

1)Function<T, R>

接口定义

  Function 接口代表一个接受一个参数并产生一个结果的函数。

  其中的 apply 方法用于执行转换或映射操作。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

使用示例如下,先声明一个将 String 转换成 LocalDateTime 的函数式方法,然后通过 apply 方法,传入 String 时间即可。

    public static void main(String[] args) {
        Function<String, LocalDateTime> convertToLocalDateTime = sourceDate -> {
            try {
                return LocalDateTime.parse(sourceDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            } catch (RuntimeException e) {
                try {
                    return LocalDate.parse(sourceDate).atStartOfDay();
                } catch (RuntimeException e1) {
                    throw new RuntimeException("时间解析失败,错误信息为:", e);
                }
            }
        };

        LocalDateTime apply = convertToLocalDateTime.apply("2024-10-10 00:00:00");
        System.out.println(apply);
    }

2)Consumer<T>

接口定义

 Consumer 接口表示一个接受单个输入参数并且不返回任何结果的操作。

 其中 accept 方法用于执行消费操作。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

使用示例如下,consume 函数与 Function 函数最大的区别就是, Consume 函数没有返回值

    public static void main(String[] args) {
        Consumer<String> consumer = sourceDate -> {
            try {
                LocalDateTime parse = LocalDateTime.parse(sourceDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                log.info("解析后的时间为{}", parse);
            } catch (RuntimeException e) {
                try {
                    LocalDateTime parse = LocalDate.parse(sourceDate).atStartOfDay();
                    log.info("解析后的时间为{}", parse);
                } catch (RuntimeException e1) {
                    throw new RuntimeException("时间解析失败,错误信息为:", e);
                }
            }
        };
        consumer.accept("2020-04-24");
    }

3)Supplier<T>

接口定义

 Supplier  接口表示一个不接受参数但可以生成结果的函数。

 其中 get 方法用于执行生成操作。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

使用示例如下,supplier 函数与 Function 函数最大的区别就是, Consume 函数没有参数,只有返回值

    public static void main(String[] args) {
        Supplier<LocalDateTime> consumer = () -> {
            String sourceDate = "2020-04-24";
            try {
                return LocalDateTime.parse(sourceDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            } catch (RuntimeException e) {
                try {
                    return LocalDate.parse(sourceDate).atStartOfDay();
                } catch (RuntimeException e1) {
                    throw new RuntimeException("时间解析失败,错误信息为:", e);
                }
            }
        };

        LocalDateTime parse = consumer.get();
        log.info("解析后的时间为{}", parse);
    }

4)Predicate<T>

接口定义

 Predicate 接口代表一个接受单个输入参数并返回布尔值的函数。

 test 方法用于执行判断操作。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

使用示例如下,该函数通过传入一个参数,做一些逻辑判断并返回 true 或者 false

    public static void main(String[] args) {
        Predicate<String> consumer = sourceDate -> {
            try {
                LocalDateTime parse = LocalDateTime.parse(sourceDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                log.info("解析后的时间为{}", parse);
                return true;
            } catch (RuntimeException e) {
                try {
                    LocalDateTime parse = LocalDate.parse(sourceDate).atStartOfDay();
                    log.info("解析后的时间为{}", parse);
                    return true;
                } catch (RuntimeException e1) {
                    log.error("时间解析失败,错误信息为:", e);
                    return false;
                }
            }
        };

        boolean test = consumer.test("2024-04-24");
        log.info("解析结果为: {}", test);
    }

5)当然还有一些进阶的函数,如:

  • UnaryOperator<T>: 接受一个参数并返回一个相同类型的对象(等同于Function<T, T>)。
  • BiFunction<T, U, R>: 接受两个参数并返回一个结果。
  • BiConsumer<T, U>: 接受两个参数但不返回结果。
  • BiPredicate<T, U>: 接受两个参数并返回一个布尔值。

6)汇总图

函数式接口参数数量返回值主要方法说明
Function<T, R>1 (T)Rapply(T t)接受一个参数 T 并返回一个结果 R。例如,将字符串转换为 LocalDateTime。
Consumer<T>1 (T)无返回值accept(T t)接受一个参数 T 并执行操作,但不返回任何结果。例如,打印或记录转换后的时间。
Supplier<T>Tget()不接受任何参数,但生成一个结果 T。例如,生成一个默认的时间。
Predicate<T>1 (T)booleantest(T t)接受一个参数 T 并返回一个布尔值,用于进行条件判断。例如,判断字符串是否可以成功解析为时间。
UnaryOperator<T>1 (T)T (相同类型)apply(T t)特殊的 Function<T, R>,接受一个参数 T 并返回相同类型的对象 T。
BiFunction<T, U, R>2 (T, U)Rapply(T t, U u)接受两个参数 T 和 U 并返回一个结果 R。例如,根据两个参数生成一个新的对象。
BiConsumer<T, U>2 (T, U)无返回值accept(T t, U u)接受两个参数 T 和 U 并执行操作,但不返回任何结果。
BiPredicate<T, U>2 (T, U)booleantest(T t, U u)接受两个参数 T 和 U 并返回一个布尔值,用于进行条件判断。

3. Stream API

Stream API是Java 8引入的用于处理集合数据的函数式编程工具,支持链式操作和惰性求值,是平时写代码非常重要的组成部分,目前在各大公司被广泛使用,如果不会 stream 流,很可能连公司代码都看不懂。

这里不做过多赘述,学习stream流可以参考:函数式编程-Stream 流_函数式编程 stream流-CSDN博客

4. Optional类

  我们知道,Java非常常见的一个异常就是空指针异常,而 Optional 是 Java 8 引入的一个用于避免空指针异常(NullPointerException)的容器类。它允许开发者更优雅地处理可能为空的值。Optional 不是一个函数式接口,但它与函数式编程有很强的关联,尤其是在处理 Stream 和集合时非常有用。

1. Optional 的基本概念

  1. Optional 的主要目的是帮助开发者更好地处理可能为 null 的值,避免显式的空值检查
  2. Optional 是一个容器,它可以包含一个非空值或不包含任何值(即 null)。
  3. Optional 提供了许多方法来安全地操作其包含的值,避免空指针异常。

2. 如何创建 Optional 对象

  • Optional.of(T value): 创建一个包含非空值的 Optional。如果传入的值为 null,会抛出 NullPointerException

    Optional<String> optional = Optional.of("Hello");
    
  • Optional.ofNullable(T value): 创建一个可以包含 null 值的 Optional。如果传入的值为 null,则返回一个空的 Optional

    Optional<String> optional = Optional.ofNullable(null);
    
  • Optional.empty(): 创建一个空的 Optional

    Optional<String> optional = Optional.empty();
    

3. 如何检查 Optional 中的值

  • isPresent(): 判断 Optional 是否包含值。

    if (optional.isPresent()) {
        System.out.println("值存在: " + optional.get());
    } else {
        System.out.println("值不存在");
    }
    
  • ifPresent(Consumer<? super T> consumer): 如果 Optional 包含值,则执行传入的 Consumer

    optional.ifPresent(value -> System.out.println("值存在: " + value));
    

4. 如何获取 Optional 中的值

  • get(): 获取 Optional 中的值。如果 Optional 为空,则抛出 NoSuchElementException

    String value = optional.get();
    
  • orElse(T other): 如果 Optional 为空,则返回传入的默认值。

    String value = optional.orElse("默认值");
    
  • orElseGet(Supplier<? extends T> supplier): 如果 Optional 为空,则执行传入的 Supplier 并返回其结果。

    String value = optional.orElseGet(() -> "通过Supplier获取的默认值");
    
  • orElseThrow(Supplier<? extends X> exceptionSupplier): 如果 Optional 为空,则抛出传入的异常。

    String value = optional.orElseThrow(() -> new IllegalArgumentException("值不存在"));
    

5. 如何转换和过滤 Optional 中的值

  • map(Function<? super T, ? extends U> mapper): 对 Optional 中的值进行映射操作。如果 Optional 为空,则返回一个空的 Optional

    Optional<String> optional = Optional.of("hello");
    Optional<Integer> lengthOptional = optional.map(String::length);
    
  • flatMap(Function<? super T, Optional<U>> mapper): 与 map 类似,但 mapper 返回的是一个 OptionalflatMap 会将嵌套的 Optional 展开。

    Optional<String> optional = Optional.of("hello");
    Optional<Integer> lengthOptional = optional.flatMap(value -> Optional.of(value.length()));
    
  • filter(Predicate<? super T> predicate): 对 Optional 中的值进行过滤。如果值不满足条件,则返回一个空的 Optional

    Optional<String> optional = Optional.of("hello");
    Optional<String> filteredOptional = optional.filter(value -> value.length() > 4);
    

6. 使用场景

  • 避免空指针异常: 在使用可能为 null 的值时,可以使用 Optional 来避免空指针异常。

    String str = null;
    Optional<String> optionalStr = Optional.ofNullable(str);
    String result = optionalStr.orElse("默认值");
    
  • 函数式编程: 在 Stream 操作中,Optional 经常用于处理可能为空的值。

    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
    Optional<String> firstLongName = names.stream()
                                          .filter(name -> name.length() > 4)
                                          .findFirst();
    firstLongName.ifPresent(System.out::println);
    

7. 注意事项

  • 不要滥用 Optional: 虽然 Optional 可以帮助避免空指针异常,但不应该在所有地方都使用它。适当的情况下使用 Optional 可以提高代码的可读性和安全性。
  • Optional 不是替代 null 的万能药: 在某些情况下,直接返回 null 可能更合适。例如,在某些场景下,null 可以更明确地表示不存在的情况。
  • 性能考虑Optional 在某些情况下可能会带来额外的性能开销,尤其是在处理大量数据时。因此,需要根据具体情况权衡使用 Optional 的利弊。

5. 总结

        通过学习 Java 的函数式编程思想,可以大大简化、高效、优雅的去处理我们的代码。熟练的掌握这些用法之后,你的代码档次,b格会提升一个台阶!

ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!

链接:https://www.yuque.com/u39298356/uu4hxh?# 《Java知识宝典》  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值