1.函数式接口
函数式接口是一个 仅包含一个抽象方法 的接口。这使得它可以用来作为 Lambda 表达式的目标类型(目标类型是指 Lambda 表达式或方法引用需要符合该接口的抽象方法签名)。
@FunctionalInterface
public interface MyFunctionalInterface {
void doSomething();
}
虽然 Java 允许在函数式接口中存在默认方法(default methods)和静态方法,但它只允许有一个抽象方法。
1. 函数式接口和普通接口的区别
| 特性 | 函数式接口 | 普通接口 |
|---|---|---|
| 抽象方法数量 | 只能有一个抽象方法 | 可以有多个抽象方法 |
| 用途 | 主要用于 Lambda 表达式或方法引用 | 用于定义类的行为规范 |
@FunctionalInterface | 可以加上这个注解,确保接口是函数式接口 | 不需要此注解 |
| 默认方法和静态方法 | 可以有,但不会影响接口是函数式接口 | 可以有默认方法和静态方法 |
| Lambda 表达式支持 | 可以通过 Lambda 表达式或方法引用实现 | 不能直接用 Lambda 表达式实现,必须通过类实现 |
2. 常见的函数式接口
Java 提供了几个常用的函数式接口,这些接口被广泛用于流(Stream API)、并发、事件处理等场景。以下是一些常见的函数式接口:
Predicate<T>:接收一个参数,返回一个布尔值。
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<T, R>:接收一个参数,返回一个结果。
Function<String, Integer> stringLength = s -> s.length();
Consumer<T>:接收一个参数,不返回结果。
Consumer<String> print = s -> System.out.println(s);
Supplier<T>:不接收参数,返回一个结果。
Supplier<Double> randomValue = () -> Math.random();
Runnable:不接收参数,不返回结果。
Runnable task = () -> System.out.println("Running a task");
3. 函数式接口的使用场景
a. Lambda 表达式
函数式接口的主要应用是与 Lambda 表达式一起使用。例如:
@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
public class LambdaExample {
public static void main(String[] args) {
// 使用 Lambda 表达式实现 Greeting 接口
Greeting greeting = (name) -> System.out.println("Hello, " + name);
greeting.sayHello("Alice");
}
}
b. 方法引用
除了 Lambda 表达式,函数式接口还支持方法引用。方法引用是一种更加简洁的表达方式,可以通过现有的方法来实现接口的抽象方法:
@FunctionalInterface
interface Printer {
void print(String message);
}
public class MethodReferenceExample {
public static void main(String[] args) {
// 使用方法引用实现 Printer 接口
Printer printer = System.out::println;
printer.print("Hello, Method Reference!");
}
}
c. Stream API
Stream API 中广泛使用了函数式接口,例如 filter、map、forEach 等操作:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A")) // Predicate
.forEach(System.out::println); // Consumer
2.Lambda表达式
Lambda 表达式 是 Java 8 引入的一个重要特性,它提供了一种简洁的方式来表示匿名函数,并让你可以像使用值一样来处理行为。
1. Lambda 表达式的基本语法
Lambda 表达式的基本语法是:
(parameters) -> expression
或者
(parameters) -> { statements }
a. 基本形式
Lambda 表达式分为三部分:
- 参数列表:括号中的参数,类似于方法的参数。可以有一个或多个参数,也可以没有参数。
- 箭头符号
->:表示从参数传递到表达式或语句体的转换。 - 表达式或代码块:右边是 Lambda 表达式的主体,既可以是单个表达式,也可以是一个代码块。
b. 示例
-
一个没有参数的 Lambda 表达式:
() -> System.out.println("Hello, World!");
- 一个带有一个参数的 Lambda 表达式:
(name) -> System.out.println("Hello, " + name);
- 一个带有多个参数并返回值的 Lambda 表达式:
(a, b) -> a + b;
- 带有多条语句的 Lambda 表达式(使用代码块):
(a, b) -> {
int sum = a + b;
System.out.println("Sum is: " + sum);
return sum;
};
2. Lambda 表达式的简化规则
- 如果参数类型可以推断出来,你可以省略参数的类型。例如,
(int a, int b)可以写成(a, b)。 - 如果只有一个参数,且类型可以推断,则可以省略圆括号。例如,
(name) ->可以写成name ->。 - 如果 Lambda 表达式的主体只有一条语句,并且没有返回值,可以省略大括号和
return关键字。例如,(a, b) -> { return a + b; }可以简化为(a, b) -> a + b;。
3. Lambda 表达式的底层机制
Lambda 表达式的底层实现机制是 函数式接口。函数式接口是指仅有一个抽象方法的接口。Lambda 表达式是对函数式接口的简化实现。
例如,Runnable 接口是一个函数式接口:
@FunctionalInterface
public interface Runnable {
void run();
}
当你使用 Lambda 表达式 () -> System.out.println("Hello") 来实现 Runnable 时,实际上你是创建了 Runnable 的一个实例,并提供了 run() 方法的具体实现。
Runnable r = () -> System.out.println("Hello");
4. Lambda 表达式的限制
尽管 Lambda 表达式在很多场景下提供了简洁的写法,但它也有一些限制:
- 只能实现函数式接口:Lambda 表达式只能用于实现具有单个抽象方法的接口,称为函数式接口。
- 不能直接引用局部变量:Lambda 表达式只能使用 final 或 effectively final 的局部变量(即未被修改过的局部变量)。
例如,以下代码会报错:
int number = 10;
Runnable r = () -> {
number = 20; // 不能修改局部变量
};
5.常见示例及解读
1. Predicate<T>
Predicate 是一个接受一个参数并返回布尔值的函数式接口。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Predicate<String> isEmpty = str -> str.isEmpty();
System.out.println(isEmpty.test("")); // 输出: true
2. Function<T, R>
Function 是一个接受一个参数并返回一个结果的函数式接口。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Hello")); // 输出: 5
3. Consumer<T>
Consumer 是一个接受一个参数并执行某些操作但不返回结果的函数式接口。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<String> printString = str -> System.out.println(str);
printString.accept("Hello, World!"); // 输出: Hello, World!
4. Supplier<T>
Supplier 是一个没有参数但返回结果的函数式接口。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Supplier<String> randomString = () -> "Random String";
System.out.println(randomString.get()); // 输出: Random String
5. BinaryOperator<T>
BinaryOperator 是一个接受两个相同类型的参数并返回相同类型的结果的函数式接口。
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
}
BinaryOperator<Integer> add = (a, b) -> a + b;
System.out.println(add.apply(5, 3)); // 输出: 8
6. BiFunction<T, U, R>
BiFunction 是一个接受两个参数并返回一个结果的函数式接口。
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(5, 3)); // 输出: 15
Predicate<String> isEmpty = str -> str.isEmpty();
解读这一个:isEmpty 的类型是Predicate<String> 这是一个函数式接口
当你写 Predicate<String> isEmpty = str -> str.isEmpty(); 时,实际上是创建了一个 Predicate<String> 类型的匿名类。这个类实现了 Predicate 接口中的 test 方法,这个方法定义了如何处理传入的 String 参数。
str -> str.isEmpty() str是传入的参数,而str.isEmpty() 则是其执行的函数,因为其是函数式接口,其中只有一个抽象方法,所以默认实现的就是test方法。
实际上当你写 str -> str.isEmpty()(即 Lambda 表达式)时,Java 会自动为你生成一个匿名类,该类实现了 Predicate<String> 接口,并实现了接口的唯一抽象方法 test(T t)。匿名类的实例是通过 Lambda 表达式创建的。
虽然是匿名类,但是其实例化后的对象却赋给了isEmpty,虽然类本身是匿名的,但对象是具名的(isEmpty 是这个对象的引用)。因此,isEmpty 是一个匿名类的实例(对象)的引用。
所以说,Lambda是对函数式接口的简化实现,Predicate<String> isEmpty = str -> str.isEmpty(); Predicate 说明了实现的式哪个接口,str 是传的参数 str.isEmpty() 是其中抽象方法的具体实现,而isEmpty则是创建过后的对象引用。就可以通过这个isEmpty调用接口中的方法isEmpty.test();
3.方法引用
方法引用的本质是Lambda 表达式的简化写法。在很多情况下,Lambda 表达式只是调用一个现有的方法,而没有其他额外的逻辑。此时,Java 提供了方法引用的语法,可以直接引用方法,而不是通过 Lambda 表达式来调用。
方法引用可以用 :: 作为操作符,将方法名称和类或对象连接起来。
再看一下Lambda (a, b) -> a + b 其()的是参数,->后的是函数(方法),本质上这就是一个函数,所以只要能提供一个函数理论上就是可以的,而通过 :: 的左右定义能够找到一个方法就是可以的。
方法引用一般有4种
静态方法引用(ClassName::staticMethod) 类名::静态方法
- 适用于静态方法的情况。
import java.util.Arrays;
import java.util.List;
public class StaticMethodReferenceExample {
// 静态方法
public static String toUpperCase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "charlie");
// 使用静态方法引用
names.stream()
.map(StaticMethodReferenceExample::toUpperCase) // 引用静态方法
.forEach(System.out::println); // 输出结果
}
}
实例方法引用(对象的实例方法)(instance::instanceMethod) 对象名::非静态方法
- 适用于某个对象的实例方法。
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "orange");
// 使用 "apple" 对象的 equals 方法的引用
boolean contains = words.stream().anyMatch("apple"::equals);
System.out.println(contains); // 输出:true
}
}
“apple” 这个字符串是String 类型的实例化,其也是一个对象。而equals则是这个对象的方法。
实例方法引用(类的实例方法)(ClassName::instanceMethod)类名::非静态方法
- 适用于某个类的实例方法引用,Lambda 表达式接收实例作为第一个参数。
其实这种还需要将对象进行实例化,因为非静态方法属于特定的类实例,在调用这些方法时,必须有一个对象来执行这些方法。
构造器引用(ClassName::new)类名::new
- 适用于构造器的引用,常用于创建对象。
import java.util.function.Function;
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class ConstructorReferenceExample {
public static void main(String[] args) {
// 使用构造器引用创建 Person 对象
Function<String, Person> personCreator = Person::new; // 带参数的构造器引用
Person person = personCreator.apply("Alice");
System.out.println(person.getName()); // 输出: Alice
}
}
可以自定义函数式接口,按照调用规则去使用方法引用
@FunctionalInterface
interface StringTransformer {
String transform(String str);
}
class TransformerUtil {
// 静态方法
public static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
// 实例方法
public String toUpperCase(String input) {
return input.toUpperCase();
}
}
public class Main {
public static void main(String[] args) {
// 静态方法引用
StringTransformer reverser = TransformerUtil::reverse;
System.out.println(reverser.transform("hello")); // 输出:olleh
// 实例方法引用
TransformerUtil util = new TransformerUtil();
StringTransformer upperCaser = util::toUpperCase;
System.out.println(upperCaser.transform("hello")); // 输出:HELLO
}
}
899

被折叠的 条评论
为什么被折叠?



