在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 中提供了许多内置的函数式接口,如 Function
、Consumer
、Supplier
、Predicate
等。
接下来我们将分别介绍一下这几种内置函数式接口的用法:
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) | R | apply(T t) | 接受一个参数 T 并返回一个结果 R。例如,将字符串转换为 LocalDateTime。 |
Consumer<T> | 1 (T) | 无返回值 | accept(T t) | 接受一个参数 T 并执行操作,但不返回任何结果。例如,打印或记录转换后的时间。 |
Supplier<T> | 无 | T | get() | 不接受任何参数,但生成一个结果 T。例如,生成一个默认的时间。 |
Predicate<T> | 1 (T) | boolean | test(T t) | 接受一个参数 T 并返回一个布尔值,用于进行条件判断。例如,判断字符串是否可以成功解析为时间。 |
UnaryOperator<T> | 1 (T) | T (相同类型) | apply(T t) | 特殊的 Function<T, R>,接受一个参数 T 并返回相同类型的对象 T。 |
BiFunction<T, U, R> | 2 (T, U) | R | apply(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) | boolean | test(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
的基本概念
Optional
的主要目的是帮助开发者更好地处理可能为null
的值,避免显式的空值检查Optional
是一个容器,它可以包含一个非空值或不包含任何值(即null
)。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
返回的是一个Optional
,flatMap
会将嵌套的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面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!