
00:00:00
外卖卷:非常划算 扫码领劵 省点小钱钱
Java 不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带
this参数的函数。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
函数式编程将函数作为基本运算单元,函数可作为变量传递,也可作为返回值。
@FunctionalInterface 注解明确标识接口为函数式接口。在理解这些具体接口之前,需要先理解函数式接口的概念。
一个函数式接口是只包含一个抽象方法的接口。Java 8 引入了 @FunctionalInterface 注解,你可以用它来标记一个接口,但这并不是强制的,编译器会根据接口的结构自动判断。
函数式接口是 Lambda 表达式的目标类型。Lambda 表达式可以被看作是实现了这个接口的匿名类的实例。
Supplier<T> () -> 结果Supplier (提供者)T get()Supplier 代表一个不接受任何参数但返回一个结果的函数。它通常用于延迟计算,或者当你需要一个值但这个值不是立即可用,或者需要按需生成时。() -> value 或 () -> expressionOptional.orElseGet() 方法就接受一个 Supplier。示例:
import java.util.function.Supplier;
import java.time.LocalDateTime;
public class SupplierExample {
public static void main(String[] args) {
// Supplier<String>: 提供一个 String 类型的结果
Supplier<String> helloSupplier = () -> "Hello from Supplier!";
System.out.println(helloSupplier.get()); // 输出:Hello from Supplier!
// Supplier<LocalDateTime>: 提供当前时间
Supplier<LocalDateTime> currentTimeSupplier = () -> LocalDateTime.now();
System.out.println("Current time: " + currentTimeSupplier.get());
// 延迟计算的例子
// 假设这是一个耗时操作
Supplier<String> expensiveOperationSupplier = () -> {
System.out.println("Executing expensive operation...");
try {
Thread.sleep(2000); // 模拟耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Expensive Result";
};
System.out.println("Ready to get expensive result...");
// 只有调用 get() 方法时,耗时操作才会被执行
String result = expensiveOperationSupplier.get();
System.out.println("Got expensive result: " + result);
}
}Function<T, R> (参数) -> 结果Function (函数)R apply(T t)Function 代表一个接受一个类型为 T 的参数并返回一个类型为 R 的结果的函数。它是最常见的转换接口。(param) -> result 或 param -> resultmap() 方法就接受一个 Function。andThen() 和 compose() 方法可以组合 Function。示例:
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
// Function<String, Integer>: 接受一个 String,返回一个 Integer
Function<String, Integer> stringLengthFunction = s -> s.length();
System.out.println("Length of 'hello': " + stringLengthFunction.apply("hello")); // 输出:5
// Function<Integer, String>: 接受一个 Integer,返回一个 String
Function<Integer, String> intToStringFunction = i -> "Number: " + i;
System.out.println(intToStringFunction.apply(123)); // 输出:Number: 123
// 组合 Function: andThen()
// 先计算长度,再转换为字符串
Function<String, String> lengthToStringFunction = stringLengthFunction.andThen(intToStringFunction);
System.out.println(lengthToStringFunction.apply("world")); // 输出:Number: 5
// 组合 Function: compose() (与 andThen() 顺序相反)
// 先转换为字符串,再计算长度 (这里不太合理,只是演示 compose)
Function<Integer, Integer> composeExample = stringLengthFunction.compose(intToStringFunction);
System.out.println(composeExample.apply(456)); // intToStringFunction("456") -> "Number: 456" -> stringLengthFunction("Number: 456") -> 10
}
}BiFunction<T, U, R> (参数1, 参数2) -> 结果BiFunction (双函数)R apply(T t, U u)BiFunction 代表一个接受两个类型分别为 T 和 U 的参数并返回一个类型为 R 的结果的函数。(param1, param2) -> resultMap.merge() 和 Map.compute() 方法就接受一个 BiFunction。示例:
import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
// BiFunction<Integer, Integer, Integer>: 接受两个 Integer,返回一个 Integer
BiFunction<Integer, Integer, Integer> addFunction = (a, b) -> a + b;
System.out.println("Sum of 5 and 3: " + addFunction.apply(5, 3)); // 输出:8
// BiFunction<String, String, String>: 接受两个 String,返回一个 String
BiFunction<String, String, String> concatFunction = (s1, s2) -> s1 + " " + s2;
System.out.println("Concatenated: " + concatFunction.apply("Hello", "World")); // 输出:Hello World
// BiFunction<String, Integer, String>: 接受 String 和 Integer,返回 String
BiFunction<String, Integer, String> repeatString = (text, times) -> {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) {
sb.append(text);
}
return sb.toString();
};
System.out.println(repeatString.apply("Ha", 3)); // 输出:HaHaHa
}
}Consumer<T> (参数) -> voidConsumer (消费者)void accept(T t)Consumer 代表一个接受一个类型为 T 的参数但不返回任何结果(void)的操作。它通常用于执行副作用,例如打印、修改状态等。(param) -> { // do something } 或 param -> System.out.println(param)forEach() 方法就接受一个 Consumer。示例:
import java.util.function.Consumer;
import java.util.Arrays;
import java.util.List;
public class ConsumerExample {
public static void main(String[] args) {
// Consumer<String>: 接受一个 String,没有返回值
Consumer<String> printConsumer = s -> System.out.println("Processing: " + s);
printConsumer.accept("apple"); // 输出:Processing: apple
// Consumer<Integer>: 接受一个 Integer,修改外部变量 (不推荐直接修改,但演示了副作用)
final int[] counter = {0}; // 数组是为了让 final 变量可变
Consumer<Integer> incrementCounter = i -> counter[0] += i;
incrementCounter.accept(10);
System.out.println("Counter after increment 10: " + counter[0]); // 输出:10
incrementCounter.accept(5);
System.out.println("Counter after increment 5: " + counter[0]); // 输出:15
// Stream API 中的 forEach 示例
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
System.out.println("--- Fruits List ---");
fruits.forEach(printConsumer); // 对每个元素应用 printConsumer
// 等价于 fruits.forEach(s -> System.out.println("Processing: " + s));
}
}BiConsumer<T, U> (参数1, 参数2) -> voidBiConsumer (双消费者)void accept(T t, U u)BiConsumer 代表一个接受两个类型分别为 T 和 U 的参数但不返回任何结果(void)的操作。(param1, param2) -> { // do something }Map.forEach() 方法就接受一个 BiConsumer 来处理键值对。示例:
import java.util.function.BiConsumer;
import java.util.HashMap;
import java.util.Map;
public class BiConsumerExample {
public static void main(String[] args) {
// BiConsumer<String, Integer>: 接受一个 String 和一个 Integer,没有返回值
BiConsumer<String, Integer> printKeyValue = (key, value) ->
System.out.println("Key: " + key + ", Value: " + value);
printKeyValue.accept("Age", 30); // 输出:Key: Age, Value: 30
// Map.forEach 示例
Map<String, Double> prices = new HashMap<>();
prices.put("Apple", 1.20);
prices.put("Banana", 0.80);
prices.put("Orange", 1.50);
System.out.println("--- Product Prices ---");
prices.forEach(printKeyValue); // 对 Map 中的每个键值对应用 printKeyValue
// 等价于 prices.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
// 另一个 BiConsumer 例子:更新两个相关的值
final Map<String, Integer> stock = new HashMap<>();
stock.put("Laptop", 10);
stock.put("Mouse", 50);
BiConsumer<String, Integer> updateStock = (product, quantity) -> {
stock.merge(product, quantity, (oldValue, newValue) -> oldValue + newValue);
System.out.println("Updated stock for " + product + ": " + stock.get(product));
};
updateStock.accept("Laptop", 5); // 增加 Laptop 库存
updateStock.accept("Keyboard", 20); // 添加 Keyboard
}
}| 接口名称 | 描述 | 抽象方法签名 | 参数数量 | 返回值类型 | Lambda 形式 | 典型应用场景 |
|---|---|---|---|---|---|---|
Supplier<T> | 无参数,返回一个结果 | T get() | 0 | T | () -> result | 延迟计算,按需生成值 |
Consumer<T> | 一个参数,无返回值 | void accept(T t) | 1 | void | param -> { ... } | 执行副作用,forEach |
BiConsumer<T, U> | 两个参数,无返回值 | void accept(T t, U u) | 2 | void | (p1, p2) -> { ... } | 遍历 Map,处理两个值 |
Function<T, R> | 一个参数,返回一个结果 | R apply(T t) | 1 | R | param -> result | 数据转换,map |
BiFunction<T, U, R> | 两个参数,返回一个结果 | R apply(T t, U u) | 2 | R | (p1, p2) -> result | 聚合两个值,复杂计算 |
Runnable | 无参数,无返回值 | void run() | 0 | void | () -> { ... } | 执行线程任务 |
Callable<V> | 无参数,返回一个结果 | V call() | 0 | V | () -> result | 带返回值的异步任务,它是 Runnable 的增强版 |
Comparator<T> | 两个参数,返回一个结果 | int compare(T o1, T o2) | 2 | int | (o1, o2) -> result | 对象排序,根据返回的整数值确定相对顺序 |
map, filter, forEach, reduce 等)。理解并熟练使用这些函数式接口是掌握 Java 8 及更高版本新特性的关键。
基本概念
.class 文件。.class 文件。class 关键字定义一个类,就能实现函数式接口的功能。使用方式
(参数) ‐> { 方法体 }new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传统方式");
}
}).start();new Thread(() -> System.out.println("Lambda 方式")).start();使用细节
return 关键字。String[] array = {"Apple", "Orange", "Banana", "Lemon"};
// 传统匿名内部类写法
Arrays.sort(array, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});String[] array = {"Apple", "Orange", "Banana", "Lemon"};
// 基础 Lambda 表达式(显式参数类型)
Arrays.sort(array, (String s1, String s2) -> {
return s1.compareTo(s2);
});String[] array = {"Apple", "Orange", "Banana", "Lemon"};
// 省略参数类型(编译器自动推断)
Arrays.sort(array, (s1, s2) -> {
return s1.compareTo(s2);
});String[] array = {"Apple", "Orange", "Banana", "Lemon"};
// 省略大括号和 return(仅当方法体为单行返回语句)
Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));String[] array = {"Apple", "Orange", "Banana", "Lemon"};
// 方法引用替代 Lambda(参数与调用方法完全匹配)
Arrays.sort(array, String::compareTo);基本概念
使用方式
方法引用的优势
方法引用的类型
| 类型 | 语法 | 示例 | Lambda 等效形式 | 参数映射规则 |
|---|---|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Integer::sum | (a, b) -> Integer.sum(a, b) | 参数直接传递 |
| 构造方法引用 | 类名::new | String::new | () -> new String() | 参数与构造方法参数匹配 |
| 未绑定实例方法引用 | 类名::实例方法名 | String::compareTo | (s1, s2) -> s1.compareTo(s2) | 第一个参数为方法调用者 |
| 已绑定实例方法引用 | 对象::实例方法名 | System.out::println | s -> System.out.println(s) | 参数作为实例方法的参数 |
各类型的区别
静态方法引用
函数式接口定义:
@FunctionalInterface
public interface Tools {
int sum(int a, int b); // 待实现的求和方法
}使用匿名内部类实现:
public class Main {
public static void main(String[] args) {
Tools tools = new Tools() {
@Override
public int sum(int a, int b) {
return a + b;
}
};
System.out.println(tools.sum(1, 2));
}
}也可以使用 Lambda 表达式:
public class Main {
public static void main(String[] args) {
Tools tools = (a, b) -> a + b;
System.out.println(tools.sum(1, 2));
}
}只不过还能更简单,因为 Integer 类中默认提供了求两个 int 值之和的静态方法:
// Integer 类中就已经有对应的实现了
public static int sum(int a, int b) {
return a + b;
}此时,我们可以直接将已有方法的实现作为函数式接口的实现:
public class Main {
public static void main(String[] args) {
Tools tools = (a, b) -> Integer.sum(a, b); // 直接使用 Integer 为我们写好的求和方法
System.out.println(tools.sum(1, 2));
}
}对应的匿名内部类的方式:
public class Main {
public static void main(String[] args) {
Tools tools = new Tools() {
@Override
public int sum(int a, int b) {
return Integer.sum(a, b); // 直接使用 Integer 为我们写好的求和方法
}
};
System.out.println(tools.sum(1, 2));
}
}我们发现,Integer.sum 的参数和返回值,跟我们在 Tools 接口中定义的完全一样,所以说我们可以直接使用方法引用:
public class Main {
public static void main(String[] args) {
Tools tools = Integer::sum;
System.out.println(tools.sum(1, 2));
}
}构造方法引用
如果要把一个 List<String> 转换为 List<Person>,应该怎么办?
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = ???传统的做法是先定义一个 ArrayList<Person>,然后用 for 循环填充这个 List:
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = new ArrayList<>();
for (String name : names) {
persons.add(new Person(name));
}要更简单地实现 String 到 Person 的转换,我们可以引用 Person 的构造方法:
// 引用构造方法
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
System.out.println(persons);
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
public String toString() {
return "Person:" + this.name;
}
}后面我们会讲到 Stream 的 map() 方法。现在我们看到,这里的 map() 需要传入的 FunctionalInterface 的定义是:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}把泛型对应上就是方法签名 Person apply(String),即传入参数 String,返回类型 Person。
而 Person 类的构造方法恰好满足这个条件,因为构造方法的参数是 String,而构造方法虽然没有 return 语句,
但它会隐式地返回 this 实例,类型就是 Person,因此,此处可以引用构造方法。
构造方法的引用写法是 类名::new,因此,此处传入 Person::new。
未绑定实例方法引用
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String[] array = {"Apple", "Orange", "Banana", "Lemon"};
Arrays.sort(array, String::compareTo);
System.out.println(String.join(", ", array));
}
}上面的代码可以编译通过,这说明 String.compareTo() 方法符合 Lambda 定义。
观察 String.compareTo() 的方法定义:
public final class String {
public int compareTo(String anotherString) {
...
}
}这个方法的签名只有一个参数,为什么和 int Comparator<String>.compare(String, String) 能匹配呢?
因为实例方法有一个隐含的 this 参数,String 类的 compareTo() 方法在实际调用的时候,第一个隐含参数总是传入 this,相当于静态方法:
public static int compareTo(String this, String anotherString);所以,String.compareTo() 方法也可作为方法引用传入。
已绑定实例方法引用
Consumer<T> 是 Java 内置的函数式接口,定义如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // 抽象方法:接受一个参数 t,无返回值
}Consumer<String> 的抽象方法 accept(String) 和 System.out.println(String) 的方法签名完全一致,
因此可以直接通过方法引用 System.out::println 来实现 Consumer<String> 接口。
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<String> consumer = System.out::println;
consumer.accept("Hello, World!"); // 直接通过方法引用调用实例方法
}
}
评论