回調
回调是一种常见的编程模式,它通过将一个可执行的函数作为参数传递给另一个函数,以在特定的要求和时间节点下执行。
回调函数可以提供一种异步的处理方式,允许代码在等待某个事件完成时继续执行其他操作
public interface Set<E> {
int size();
boolean isEmpty();
void clear();
boolean contains(E element);
void add(E element);
void remove(E element);
void traversal(Visitor<E> visitor);
public static abstract class Visitor<E> {
boolean stop = false;
public abstract boolean visit(E element);
}
}
ListSet.traversal(new Visitor<Integer>() {
@Override
public boolean visit(Integer element) {
System.out.println(element);
return stop;
}
});
重写接口函数 visit
实现新特定的遍历要求,属于他特定要求下执行的回調
回调模式在很多场景下都有使用,在Java中也有许多例子,如事件监听器、异步任务等。它带来了灵活性和可扩展性,可以让代码更加模块化
异步任务中的回調
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
public class Example {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 创建线程池
// ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务并返回Future
Future<Integer> futureResult = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 在新线程中执行的耗时操作
Thread.sleep(5000);
int result = 1 + 1;
// 返回操作结果
return result;
}
});
// 注册回调函数
futureResult.thenAccept(new Consumer<Integer>() {
@Override
public void accept(Integer result) {
// 在任务完成后执行的操作,result是任务的返回结果
System.out.println("Task is complete, and the result is: " + result);
}
});
// 主线程继续执行其他操作
System.out.println("Do something else while waiting for the task to complete.");
// 关闭线程池
executorService.shutdown();
}
}
执行顺序:
- 创建一个固定大小为3的线程池
executorService
。 - 提交一个任务到线程池,并返回一个
Future
对象futureResult
,该对象可用于获取任务结果或添加回调函数。 - 注册一个回调函数给
futureResult
,当任务完成时执行该函数。 - 主线程继续执行其他操作,输出提示信息:"Do something else while waiting for the task to complete."。
- 在后台线程中执行的任务开始执行,由于添加了
Thread.sleep(5000)
语句,任务会暂停执行5秒钟。 - 主线程中的代码继续执行,此时任务仍在执行中,因此主线程会继续执行其他操作。
- 5秒钟后,任务完成,触发回调函数。回调函数输出任务的结果信息:"Task is complete, and the result is: 2"。
- 主线程继续执行其他操作,最后关闭线程池。
异步:
接着上面
上述代码中,创建了一个固定大小为3的线程池(ExecutorService),但实际上只有两条线程被使用,主线程和线程池中的任务线程
thenAccept
是 Java
中的一个方法,属于 CompletableFuture
类的一种组合式异步编程的方法。在异步编程中,通常需要编写回调函数以在任务完成后执行一些操作。thenAccept
方法正是一个这样的回调函数。在实际使用中,我们可以调用 Future.thenAccept
方法,给 Future
对象注册一个回调函数,以在任务完成后执行该回调函数。
thenAccept
方法的签名如下:
public CompletionStage<Void> thenAccept(Consumer<? super T> action)
其中,参数 action
是一个 Consumer
对象,表示该回调函数将接受一个类型为 T 的参数,但不返回任何值(即无返回值的函数)。在任务完成后,thenAccept
方法将自动将任务结果传递给 action
函数进行处理,从而实现回调函数的功能。
需要注意的是,thenAccept
方法返回CompletionStage<Void>
对象,表示一个任务完成后,会为下一个任务触发完成信号的 future。这个对象通常只用于流畅式编程或组合式异步编程的场景中,可以通过链式调用多个 thenAccept
方法,并最终调用 CompletableFuture.join()
或CompletableFuture.get()
方法,等待所有子任务完成再进行下一步操作。
总结:
- Callable 是 Java 中的函数式接口,它的抽象方法是 call(),不接受参数,有返回值
- Consumer 也是 Java 中的函数式接口,它的抽象方法是 accept(),接受一个参数,无返回值
都适合在多线程环境中执行耗时任务,一个有返回值一个没有返回值
futrue对象
Future
对象是 Java 中多线程编程中非常重要的概念,用于表示一个异步计算任务的未来结果。当一个任务被提交给线程池或其它执行服务时,将返回一个 Future
对象,它可以用于检查任务是否完成,获取任务的执行结果,以及等待任务完成。在调用 Future
对象的 get()
方法时,如果任务还未完成,当前线程将会阻塞,直到任务完成并返回结果,或者在等待过程中抛出异常或超时。
在 Java 5 中,标准库引入了 java.util.concurrent.Future
接口,用于表示一些异步执行的结果。但是,Java 5 中的 Future 接口仅支持了一些非常基本的操作,比如检查任务是否完成以及获取任务结果等。而在 Java 8 中,标准库进一步扩展了 Future
接口,增加了一些新的方法,比如 thenApply
、thenAccept
、thenRun
等,可以实现更为丰富的组合式异步编程,这就是 CompletableFuture
类。
需要注意的是,Future
对象虽然能够异步地执行某些任务,但它并不是线程,只是一个任务的占位符,不能直接被用于线程同步或共享变量等操作。如果需要共享变量或线程同步,应该使用 Lock
、Semaphore
等线程同步工具或者使用 volatile
、Atomic
、synchronized
等机制。
另外,当使用 Future
对象时,需要小心潜在的线程安全问题,特别是在多个任务之间共享 Future
对象时更是如此。为了避免线程安全问题,建议对共享的 Future 对象进行合理的锁或同步操作。
组合式异步编程
组合式异步编程是一种并发编程的模式,在这种模式下,你可以通过组合多个异步任务,形成复杂的异步操作流水线,并在需要时触发执行。
在 Java 中,CompletableFuture
类提供了丰富的方法来支持组合式异步编程。通过使用 CompletableFuture
,你可以将多个异步任务连接起来,构建一个异步任务的有向无环图(DAG)。
下面是一些常见的 CompletableFuture
方法,可用于构建组合式异步编程的流水线:
-
thenApply
:对前一个任务的结果应用函数,并返回一个新的CompletableFuture
对象,表示应用函数后的结果。 -
thenAccept
:对前一个任务的结果应用消费函数,并执行指定的操作,没有返回值。 -
thenRun
:在前一个任务完成后执行一个Runnable,没有返回值。 -
thenCompose
:将前一个任务的结果作为输入,返回一个新的CompletableFuture
对象,表示后续任务。 -
thenCombine
:将两个CompletableFuture
的结果进行组合,并返回一个新的CompletableFuture
对象。 -
allOf
:等待所有给定的CompletableFuture
对象都完成后返回一个新的CompletableFuture
对象。 -
anyOf
:等待任一给定的CompletableFuture
对象完成后返回一个新的CompletableFuture
对象。
通过组合这些方法,你可以创建一个复杂的异步操作流水线,使得多个任务按照特定的顺序和条件进行执行。
callable中call和runnable中run
Callable 接口是 Java 5 中引入的一种新式接口,用于支持带返回值的多线程操作。该接口只包含一个方法 call(),它类似于 Runnable 接口中的 run() 方法,但 call() 方法有返回值并且抛出异常。
call() 方法的声明如下:
T call() throws Exception;
其中,T 是返回值类型,Exception 是可能抛出的异常类型。call() 方法返回一个对象,表示该任务执行的结果。
而Runnable 的 run() 方法没有返回值
void run();
lambda
函数式接口
是Java 8引入的一个概念,它是指只包含一个抽象方法
的接口。函数式接口的意义在于可以作为lambda表达式的目标类型
,从而实现更加简洁和灵活的代码编写方式,而无需显式地创建和实现具体的类。
Lambda表达式是一种匿名函数,它可以作为方法参数、返回值或变量使用。在大多数情况下,lambda表达式可以与函数式接口一起使用,根据接口定义的唯一抽象方法,推断其参数和返回类型。通过使用lambda表达式,我们可以以更少的代码实现接口的方法。
函数式接口和lambda的联系非常紧密,它们共同构成了Java函数式编程
的基础。函数式接口提供了定义功能的规范,而lambda表达式提供了一种简洁、灵活和直观的方式来实现这些功能。它们使得在Java中编写函数式风格的代码变得更加方便和可读。
实例1
import java.util.function.IntSupplier;
class MyIntSupplier implements IntSupplier {
@Override
public int getAsInt() {
return 10;
}
}
public class Main {
public static void main(String[] args) {
IntSupplier supplier = new MyIntSupplier();
int result = supplier.getAsInt(); // 返回 10
System.out.println(result);
}
}
化简
IntSupplier supplier = () -> 10;
int result = supplier.getAsInt(); // 返回 10
实例2
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = filterEven(numbers); // 过滤偶数
System.out.println(evenNumbers);
}
public static List<Integer> filterEven(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
result.add(number);
}
}
return result;
}
}
化简
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Predicate<Integer> isEven = n -> n % 2 == 0;
List<Integer> evenNumbers = filter(numbers, isEven); // 过滤偶数
System.out.println(evenNumbers);
}
public static List<Integer> filter(List<Integer> numbers, Predicate<Integer> predicate) {
List<Integer> result = new ArrayList<>();
for (Integer number : numbers) {
if (predicate.test(number)) {
result.add(number);
}
}
return result;
}
}
Java 8 引入了其他一些函数式接口
Consumer:Consumer 接口表示接受一个输入参数并且不返回任何结果的操作。它包含一个名为
accept
的抽象方法,接收泛型参数类型的对象。示例:Consumer<String> printer = message -> System.out.println(message);
Supplier:Supplier 接口代表一个提供结果的提供者。它不接收任何输入参数,返回泛型参数类型的对象。示例:
Supplier<Integer> randomNumber = () -> (int) (Math.random() * 100);
Function:Function 接口代表一个接受一个输入参数并返回结果的函数。它包含一个名为
apply
的抽象方法,接收一个泛型参数类型的对象,并返回一个泛型参数类型的结果。示例:Function<Integer, String> convertToString = number -> String.valueOf(number);
Predicate:Predicate 接口表示一个接受一个输入参数并返回布尔值的断言函数。它包含一个名为
test
的抽象方法,接收一个泛型参数类型的对象,并返回一个布尔值。示例:Predicate<Integer> isEven = number -> number % 2 == 0;
UnaryOperator:UnaryOperator 接口代表对单个操作数的操作,并返回该操作数的结果。它扩展自 Function 接口,其输入类型和返回类型相同。示例:
UnaryOperator<Integer> square = number -> number * number;
BinaryOperator:BinaryOperator 接口代表对两个操作数的操作,并返回该操作的结果。它扩展自 BiFunction 接口,其两个输入类型和返回类型相同。示例:
BinaryOperator<Integer> sum = (a, b) -> a + b;
这些函数式接口提供了不同的功能,并广泛用于函数式编程和Lambda表达式的场景中。你可以根据具体的需求选择适当的函数式接口来操作和处理数据。