java8函数式编程

发布时间 2023-06-06 20:24:59作者: 华府家丁9527

1.什么是函数式编程

每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

2.Lambda表达式

Stream<String> stream = Stream.of("张三","李四");

2.1 collect(toList())

List<String> names = stream.collect(Collectors.toList());

2.2 map

Stream<Integer> nameLen = stream.map(name -> return name.length());

2.3 filter

Stream<String> name = stream.filter(name -> return "张三".equals(name));

2.4 Optional

Optional是Java8新加入的一个容器,这个容器 只存1个或0个元素,它用于防止出现NullPointException,它提供如下方法:
isPresent(): 判断容器中是否有值。
ifPresent(Consume lambda): 容器若不为空则执行括号中的Lambda表达式。
T get(): 获取容器中的元素,若容器为空则抛出NoSuchElement异常。
T orElse(T other): 获取容器中的元素,若容器为空则返回括号中的默认值。
of和ofNullable方法
of 和 ofNullable方法都用于创建包含值的 Optional对象。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出NullPointerException。你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()。 如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法。
//将user对象放入容器中,但是不清楚user是否为null,如果user不是null,那么获取User的年龄,如果年龄为null,则默认是18岁。
Integer userAge = Optional.ofNullable(user).map(User::getAge).orElse(18);

3.异步编程

3.1 异步编程函数式接口

Callable、Runnable、Future、CompletableFuture

3.1.1 Callable与Runnable

public interface Runnable {
public abstract void run();
}
public interface Callable<V> {
V call() throws Exception;
}

3.1.2 Future

如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务,处理完成后,再通过Future获取计算结果。
Future模式也有它的缺点,它没有提供通知的机制,我们无法得知Future什么时候完成。如果要在future.get()的地方等待future返回的结果,那只能通过isDone()轮询查询。

3.1.3 CompletableFuture

CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。
CompletableFuture创建异步任务,一般有supplyAsyncrunAsync两个方法
  • supplyAsync执行CompletableFuture任务,支持返回值
  • runAsync执行CompletableFuture任务,没有返回值
//可以自定义线程池
ExecutorService executor = Executors.newCachedThreadPool();
//runAsync的使用
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> System.out.println("run,shawn"), executor);
//supplyAsync的使用
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
System.out.print("supply,shawn");
return "shawn"; }, executor);
//runAsync的future没有返回值,输出null
System.out.println(runFuture.join());
//supplyAsync的future,有返回值
System.out.println(supplyFuture.join());
executor.shutdown(); // 线程池需要关闭
 
  • thenRun/thenRunAsync
  • 做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值
  • thenAccept/thenAcceptAsync
  • 第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。
  • thenApply/thenApplyAsync
  • 第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。
注意:上述三个方法如果执行第一个任务的时候,传入了一个自定义线程池:
  • 调用xxx方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池
  • 调用xxxAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(
()->{
System.out.println("原始CompletableFuture方法任务");
return "shawn";
}
);
CompletableFuture<String> thenApplyFuture = orgFuture.thenApply((a) -> {
if ("shawn".equals(a)) {
return "true";
}
return "false";
});
System.out.println(thenApplyFuture.get());
  • exceptionally
  • 某个任务执行异常时,执行的回调方法;并且有抛出异常作为参数,传递到回调方法
  • whenComplete
  • 某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFuture的result是上个任务的结果
  • handle
  • 某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFuture的result是回调方法执行的结果
CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(
()->{
System.out.println("当前线程名称:" + Thread.currentThread().getName());
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "shawn";
}
);
//exceptionally使用方法是orgFuture.exceptionally((e)->{...})
CompletableFuture<String> rstFuture = orgFuture.handle((a, throwable) -> {
System.out.println("上个任务执行完啦,还把" + a + "传过来");
if ("shawn".equals(a)) {
System.out.println("666");
//whenComplete没有返回
return "ok";
}
System.out.println("333");
return null;
});
System.out.println(rstFuture.get());
}
 
  • AND组合关系
  • thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务。
  • 区别在于:
  • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
  • thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
  • runAfterBoth 不会把执行结果当做方法入参,且没有返回值。
  • OR组合关系
  • applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。
  • 区别在于:
  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • runAfterEither:不会把执行结果当做方法入参,且没有返回值。
//第一个异步任务,休眠2秒,保证它执行晚点
CompletableFuture<String> first = CompletableFuture.supplyAsync(()->{
try{
Thread.sleep(2000L);
System.out.println("执行完第一个异步任务");}
catch (Exception e){
return "第一个任务异常";
}
return "第一个异步任务";
});
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<Void> future = CompletableFuture
//第二个异步任务
.supplyAsync(() -> {
System.out.println("执行完第二个任务");
return "第二个任务";}
, executor)
//第三个任务
.acceptEitherAsync(first, System.out::println, executor);
executor.shutdown();
  • thenCompose
  • thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例
  • 如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
  • 如果该CompletableFuture实例为null,然后就执行这个新任务
//举个例子
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> f = CompletableFuture.completedFuture("第一个任务");
//第二个异步任务
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "第二个任务", executor)
.thenComposeAsync(data -> {
System.out.println(data); return f; //使用第一个任务作为返回
}, executor);
//get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出。而 join() 会抛出未经检查的异常。
System.out.println(future.join());
executor.shutdown();
}

3.2 CompletableFuture使用注意点

  • Future需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。小伙伴们使用的时候,注意一下哈,考虑是否加try...catch...
  • CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间
  • CompletableFuture代码中又使用了默认的线程池,处理的线程个数是电脑CPU核数-1。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。
  • 如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离