5.1 概述
只有一个抽象方法的接口我们称之为函数接口。
JDK的函数式接口都加上了 @FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
在Java中,抽象方法是一种没有方法体(实现代码)的方法,只有声明,而没有具体的实现。它用关键字 abstract
进行声明,通常存在于抽象类或接口中。抽象方法的存在是为了让子类或实现类强制性地提供对这个方法的具体实现。
以下是抽象方法的一些特点和规则:
-
没有方法体: 抽象方法只有方法声明,没有具体的实现代码。
-
关键字
abstract
: 抽象方法使用abstract
关键字进行声明。 -
存在于抽象类或接口中: 抽象方法通常存在于抽象类或接口中。如果一个类中包含抽象方法,那么该类必须被声明为抽象类。
-
强制子类实现: 子类继承抽象类或实现接口时,必须提供抽象方法的具体实现,否则子类也必须声明为抽象类。
下面是一个简单的例子,演示了抽象方法的使用:
abstract class Shape {// 抽象方法,表示计算面积的操作abstract double calculateArea();
}class Circle extends Shape {double radius;Circle(double radius) {this.radius = radius;}// 实现抽象方法@Overridedouble calculateArea() {return Math.PI * radius * radius;}
}class Rectangle extends Shape {double length;double width;Rectangle(double length, double width) {this.length = length;this.width = width;}// 实现抽象方法@Overridedouble calculateArea() {return length * width;}
}public class Main {public static void main(String[] args) {Circle circle = new Circle(5);System.out.println("Circle Area: " + circle.calculateArea());Rectangle rectangle = new Rectangle(4, 6);System.out.println("Rectangle Area: " + rectangle.calculateArea());}
}
在上述例子中,Shape
类是一个抽象类,包含了一个抽象方法 calculateArea
。Circle
和 Rectangle
类都继承自 Shape
类,并分别实现了 calculateArea
方法。这样,通过抽象方法,我们可以在不同的子类中提供特定的实现,实现多态性。
5.2 常见函数式接口
在Java中,函数式接口是一个只包含一个抽象方法的接口。从Java 8开始,引入了lambda表达式和函数式接口的概念,使得函数式编程变得更加便利。以下是一些常见的函数式接口:
-
Runnable:
- 方法签名:
void run()
- 用于表示可以在一个线程中运行的代码块。
- 方法签名:
-
Callable:
- 方法签名:
V call() throws Exception
- 与
Runnable
类似,但可以返回一个结果并抛出异常。
- 方法签名:
-
Comparator:
- 方法签名:
int compare(T o1, T o2)
- 用于比较两个对象。常用于排序算法或集合操作。
- 方法签名:
-
ActionListener:
- 方法签名:
void actionPerformed(ActionEvent e)
- 用于处理图形用户界面 (GUI) 中的动作事件。
- 方法签名:
-
Callable:
- 方法签名:
V call() throws Exception
- 用于表示一个可以调用并返回结果或抛出异常的任务。
- 方法签名:
-
Supplier:
- 方法签名:
T get()
- 用于提供一个值。通常在延迟计算中使用,只有在需要时才计算。
- 方法签名:
-
Consumer:
- 方法签名:
void accept(T t)
- 用于接受一个参数并执行某些操作,没有返回值。
- 方法签名:
-
Function:
- 方法签名:
R apply(T t)
- 用于接受一个参数并返回结果。
- 方法签名:
-
Predicate:
- 方法签名:
boolean test(T t)
- 用于测试某个条件是否满足,返回一个布尔值。
- 方法签名:
-
BiFunction:
- 方法签名:
R apply(T t, U u)
- 与
Function
类似,但接受两个参数。
- UnaryOperator:
- 方法签名:
T apply(T t)
- 表示一元操作,接受一个参数并返回结果,参数和返回值的类型相同。
- BinaryOperator:
- 方法签名:
T apply(T t1, T t2)
- 表示二元操作,接受两个参数并返回结果,参数和返回值的类型相同。
这些函数式接口在java.util.function
包中定义。它们为Lambda表达式提供了一种方便的方式,使得在Java中进行函数式编程变得更加简洁和灵活。
- Consumer 消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
Consumer
是 Java 中的一个函数式接口,用于表示接受一个参数并在处理后不返回任何结果的操作。它包含一个名为 accept
的抽象方法,该方法接受一个参数,执行某些操作,但没有返回值。
接口定义如下:
@FunctionalInterface
public interface Consumer<T> {void accept(T t);// 其他默认方法和静态方法省略
}
Consumer
接口的泛型类型 T
表示参数的类型。你可以使用 Consumer
接口来执行各种消费操作,例如打印、修改对象的状态等。
以下是一个简单的例子,演示了如何使用 Consumer
接口:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;public class ConsumerExample {public static void main(String[] args) {// 创建一个列表List<String> names = new ArrayList<>();names.add("Alice");names.add("Bob");names.add("Charlie");// 使用 Consumer 接口打印每个名称Consumer<String> printName = (String name) -> System.out.println(name);// 遍历列表,并对每个元素执行 Consumer 操作names.forEach(printName);}
}
在这个例子中,我们创建了一个 Consumer
对象 printName
,它负责打印字符串。然后,我们使用 forEach
方法遍历名字列表,并对每个名字应用 printName
操作。
注意:由于 Consumer
是一个函数式接口,因此可以使用 lambda 表达式来简化匿名类的创建。在上述例子中,(String name) -> System.out.println(name)
就是一个 Consumer
的 lambda 表达式。
- Function 计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
Function
是 Java 中的一个函数式接口,用于表示接受一个参数并返回结果的操作。它包含一个名为 apply
的抽象方法,该方法接受一个参数并返回计算的结果。
接口定义如下:
@FunctionalInterface
public interface Function<T, R> {R apply(T t);// 其他默认方法和静态方法省略
}
Function
接口的泛型类型 T
表示输入的参数类型,而泛型类型 R
表示返回值的类型。你可以使用 Function
接口来执行各种转换和计算操作。
以下是一个简单的例子,演示了如何使用 Function
接口:
import java.util.function.Function;public class FunctionExample {public static void main(String[] args) {// 创建一个 Function 对象,用于将字符串转换为整数Function<String, Integer> stringToInteger = Integer::parseInt;// 使用 apply 方法进行转换Integer result = stringToInteger.apply("123");System.out.println("Result: " + result); // 输出: Result: 123}
}
在这个例子中,我们创建了一个 Function
对象 stringToInteger
,它负责将字符串转换为整数。然后,我们使用 apply
方法将字符串 "123"
应用于 stringToInteger
,得到转换后的整数结果。
由于 Function
是一个函数式接口,因此可以使用 lambda 表达式来简化匿名类的创建。在上述例子中,Integer::parseInt
就是一个 Function
的 lambda 表达式。
- Predicate 判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
Predicate
是 Java 中的一个函数式接口,用于表示一个断言(判断条件),接受一个参数并返回一个布尔值。它包含一个名为 test
的抽象方法,该方法接受一个参数并返回一个布尔值,表示是否满足某个条件。
接口定义如下:
@FunctionalInterface
public interface Predicate<T> {boolean test(T t);// 其他默认方法和静态方法省略
}
Predicate
接口的泛型类型 T
表示输入的参数类型。你可以使用 Predicate
接口来进行各种条件判断,例如筛选集合元素、过滤数据等。
以下是一个简单的例子,演示了如何使用 Predicate
接口:
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;public class PredicateExample {public static void main(String[] args) {// 创建一个 Predicate 对象,用于判断字符串是否包含字母 "a"Predicate<String> containsA = s -> s.contains("a");// 创建一个字符串列表List<String> words = Arrays.asList("apple", "banana", "orange", "grape");// 使用 Predicate 进行筛选System.out.println("Words containing 'a':");for (String word : words) {if (containsA.test(word)) {System.out.println(word);}}}
}
在这个例子中,我们创建了一个 Predicate
对象 containsA
,它负责判断字符串是否包含字母 “a”。然后,我们使用该 Predicate
对象对字符串列表进行筛选,只输出包含字母 “a” 的单词。
由于 Predicate
是一个函数式接口,因此可以使用 lambda 表达式来简化匿名类的创建。在上述例子中,s -> s.contains("a")
就是一个 Predicate
的 lambda 表达式。
- Supplier 生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回。
Supplier
是 Java 中的一个函数式接口,用于表示一个生产者,不接受任何参数但返回一个结果。它包含一个名为 get
的抽象方法,该方法不接受任何参数并返回一个结果。
接口定义如下:
@FunctionalInterface
public interface Supplier<T> {T get();// 其他默认方法和静态方法省略
}
Supplier
接口的泛型类型 T
表示返回值的类型。你可以使用 Supplier
接口来生成或提供数据,通常在需要延迟计算或按需生成值的场景中使用。
以下是一个简单的例子,演示了如何使用 Supplier
接口:
import java.util.function.Supplier;public class SupplierExample {public static void main(String[] args) {// 创建一个 Supplier 对象,用于提供随机数Supplier<Integer> randomSupplier = () -> (int) (Math.random() * 100);// 获取并输出随机数int randomNumber = randomSupplier.get();System.out.println("Random Number: " + randomNumber);}
}
在这个例子中,我们创建了一个 Supplier
对象 randomSupplier
,它负责生成一个介于 0 到 100 之间的随机整数。然后,我们使用 get
方法获取并输出这个随机数。
由于 Supplier
是一个函数式接口,因此可以使用 lambda 表达式来简化匿名类的创建。在上述例子中,() -> (int) (Math.random() * 100)
就是一个 Supplier
的 lambda 表达式。
5.3 常用的默认方法
and
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件。
例如:
打印作家中年龄大于17并且姓名的长度大于1的作家。
List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge()>17;}}.and(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getName().length()>1;}})).forEach(author -> System.out.println(author));
or
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用 || 来拼接两个判断条件。
例如:
打印作家中年龄大于17或者姓名的长度小于2的作家。
// 打印作家中年龄大于17或者姓名的长度小于2的作家。List<Author> authors = getAuthors();authors.stream().filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge()>17;}}.or(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getName().length()<2;}})).forEach(author -> System.out.println(author.getName()));
negate
Predicate接口中的方法。negate方法相当于是在判断添加前面加了个! 表示取反
例如:
打印作家中年龄不大于17的作家。
// 打印作家中年龄不大于17的作家。List<Author> authors = getAuthors();authors.stream().filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge()>17;}}.negate()).forEach(author -> System.out.println(author.getAge()));