Java8函数式编程入门与实践
立即解锁
发布时间: 2025-08-18 00:56:21 阅读量: 3 订阅数: 3 

# Java 8 函数式编程入门与实践
## 1. 采用函数式编程风格
在为企业应用创建实际代码时,性能是需要关注的重要方面。采用函数式编程风格虽然语法学习相对容易,但改变设计和思考方式则需要更多努力。Java 中的函数式编程是一种范式转变,有很多例子表明这种转变是有益的。
### 1.1 平稳过渡到函数式编程
遵循一些实践有助于更好地进行函数式编码。Java 现在是一种混合范式语言,支持命令式、面向对象和函数式编程,需要明智地平衡它们,而这种能力来自经验、尝试不同设计和评估权衡。
在过渡初期,继续以熟悉的方式思考是很自然的。可以先实现代码,然后快速重构,遵循“先让它工作,然后尽快让它变得更好”的原则。随着经验的积累,重构的需求会减少,函数式风格的代码会更自然地产生。
### 1.2 提升编程能力的途径
要提升编程能力,需要愿意改变方式。要无畏地尝试新想法,并根据同事的反馈进行改进。可以从战术代码审查、结对编程和工作中的交流会议中受益。工作之外,当地的 Java 用户组等特殊兴趣小组是扩展知识的好地方,可以参加当地的学习小组,若没有则可以帮忙组织。
Java 8 和 lambda 表达式改善了软件开发方式,为最流行的语言注入了新的活力,对于程序员来说是令人兴奋的时代。
## 2. 常用函数式接口入门
JDK 8 有许多函数式接口,下面介绍一些常用的入门接口,它们都位于 `java.util.function` 包中。
| 接口名称 | 描述 | 抽象方法 | 默认方法 | 常见用法 | 原始类型特化 |
| --- | --- | --- | --- | --- | --- |
| `Consumer<T>` | 表示接受一个输入但不返回任何结果的操作,通常会产生副作用 | `accept()` | `andThen()` | 作为 `forEach()` 方法的参数 | `IntConsumer`, `LongConsumer`, `DoubleConsumer`, … |
| `Supplier<T>` | 一个工厂,预期返回新实例或预创建的实例 | `get()` | - | 用于创建惰性无限流,以及作为 `Optional` 类的 `orElseGet()` 方法的参数 | `IntSupplier`, `LongSupplier`, `DoubleSupplier`, … |
| `Predicate<T>` | 用于检查输入参数是否满足某些条件 | `test()` | `and()`, `negate()`, `or()` | 作为流的方法(如 `filter()` 和 `anyMatch()`)的参数 | `IntPredicate`, `LongPredicate`, `DoublePredicate`, … |
| `Function<T, R>` | 一个转换接口,表示接受一个参数并返回适当结果的操作 | `apply()` | `andThen()`, `compose()` | 作为流的 `map()` 方法的参数 | `IntFunction`, `LongFunction`, `DoubleFunction`, `IntToDoubleFunction`, `DoubleToIntFunction`, … |
## 3. 函数式编程语法概述
### 3.1 定义函数式接口
函数式接口必须有一个抽象(未实现)方法,可以有零个或多个默认方法或已实现的方法,也可以有静态方法。示例如下:
```java
@FunctionalInterface
public interface TailCall<T> {
TailCall<T> apply();
default boolean isComplete() { return false; }
//...
}
```
### 3.2 创建 Lambda 表达式
#### 3.2.1 无参数 Lambda 表达式
如果 Lambda 表达式没有参数,参数列表周围的括号 `()` 是必需的,`->` 用于分隔参数和 Lambda 表达式的主体。示例:
```java
lazyEvaluator(() -> evaluate(1), () -> evaluate(2));
```
#### 3.2.2 单参数 Lambda 表达式
Java 编译器可以根据上下文推断 Lambda 表达式的类型。在某些情况下,如果上下文不足以推断或需要更清晰的表达,可以在参数名前指定类型。示例:
```java
friends.forEach((final String name) -> System.out.println(name));
```
如果不提供参数类型,Java 编译器会尝试推断。如果为一个参数指定了类型,则必须为 Lambda 表达式中的所有参数指定类型。当 Lambda 表达式只有一个参数且类型被推断时,参数周围的括号 `()` 是可选的,建议使用更简洁的形式。示例:
```java
friends.forEach(name -> System.out.println(name));
```
#### 3.2.3 多参数 Lambda 表达式
如果 Lambda 表达式有多个参数或没有参数,参数列表周围的括号 `()` 是必需的。示例:
```java
friends.stream()
.reduce((name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
```
#### 3.2.4 调用带有混合参数的方法
方法的参数可以是常规类、基本类型和函数式接口的混合。方法的任何参数都可以是函数式接口,可以传递 Lambda 表达式或方法引用作为其参数。示例:
```java
friends.stream()
.reduce("Steve", (name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
```
### 3.3 存储 Lambda 表达式
为了便于重用和避免重复,通常会将 Lambda 表达式存储在变量中。示例:
```java
final Predicate<String> startsWithN = name -> name.startsWith("N");
```
### 3.4 创建多行 Lambda 表达式
虽然应尽量保持 Lambda 表达式简短,但也可以包含几行代码。使用多行代码时,需要使用花括号 `{}`,如果 Lambda 表达式预期返回值,则需要使用 `return` 关键字。示例:
```java
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("how");
writerEAM.writeStuff("sweet");
});
```
### 3.5 返回 Lambda 表达式
如果方法的返回类型是函数式接口,可以从方法的实现中返回 Lambda 表达式。示例:
```java
public static Predicate<String> checkIfStartsWith(final String letter) {
return name -> name.startsWith(letter);
}
```
### 3.6 从 Lambda 表达式返回 Lambda 表达式
可以构建返回 Lambda 表达式的 Lambda 表达式。示例:
```java
final Function<String, Predicate<String>> startsWithLetter =
letter -> name -> name.startsWith(letter);
```
### 3.7 闭包中的词法作用域
在 Lambda 表达式中可以访问封闭方法作用域内的变量。绑定到封闭作用域变量的 Lambda 表达式称为闭包。示例:
```java
public static Predicate<String> checkIfStartsWith(final String letter) {
return name -> name.startsWith(letter);
}
```
### 3.8 使用方法引用
方法引用可以替代 Lambda 表达式,当它直接将参数作为目标传递给简单方法调用时。以下是不同类型的方法引用示例:
#### 3.8.1 实例方法的方法引用
```java
friends.stream()
.map(String::toUpperCase);
// 等价于
friends.stream()
.map(name -> name.toUpperCase());
```
#### 3.8.2 静态方法的方法引用
```java
str.chars()
.filter(Character::isDigit);
// 等价于
str.chars()
.filter(ch -> Character.isDigit(ch));
```
#### 3.8.3 另一个实例上的方法的方法引用
```java
str.chars()
.forEach(System.out::println);
// 等价于
str.chars()
.forEach(ch -> System.out.println(ch
```
0
0
复制全文
相关推荐










