jdk8

182 阅读7分钟

Stream 类集合api

内部迭代
流只能被操作一次,即Stream user 只能被使用一次
中间操作:filter、map、limit、sorted、distinct、skip、mapToInt、mapToDouble、mapToLong(转换为数值流)
终端操作:forEach、count、collect

构建流

Stream<String> of = Stream.of("a", "b", "c");
of.map(String::toUpperCase).forEach(System.out::println);

int[] nums = {1, 2, 3};
int sum = Arrays.stream(nums).sum();

并行流

stream.parallel()  // 变成并行流
    .filter(...) 
    .sequential()     // 变成顺序流
    .map(...) 
    .parallel() 
    .reduce();

并行流内部使用ForkJoinPool(jdk7)它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().available.Processors()得到的。

改变线程数量,全局配置,将影响代码中所有的并行流,不建议修改它

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");

文件构成流

long uniqueWords = 0; 
// try中的流会自动关闭
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ 
	uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
 	.distinct() 
 	.count(); 
} 
catch(IOException e){ 
 
}

过滤、排序、集合

// 默认按照升序排列
List<String> userIds = users.stream().filter(x -> x.getAge > 20).sorted(comparing(User::getAge)).map(User::getId).collect(Collectors.toList);

集合转换为map

// map分组
Map<Integer, List<User>> agetToUsersMap = users.stream().collect(groupingBy(User::getAge));

// map的key必须唯一,有可能出现不唯一的时候,就会报错
// Function.identity() 表示自身
Map<String, Integer> nameToNameMap = users.stream().collect(
Collectors.toMap(User::getName, User::getAge, (oldValue, newValue) -> oldValue)); 

// 转为TreeMap,其他类似
Map<String, Integer> nameToNameMap = users.stream().collect(
Collectors.toMap(User::getName, User::getAge,
(oldValue, newValue) -> oldValue, TreeMap::new); 

数组转为stream

String[] arrays = {"hello", "world"};
Stream<String> streams = Arrays.stream(arrays);

flatMap

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容

所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流

相当于将多个Stream 合并为一个Stream,然后进行操作

List<String> uniqueCharacters = arrays.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
使用map(Arrays::stream) 返回类型:Stream<String>
返回值:["H","e","l", "o","W","r","d"]

查找与匹配

  • anyMatch 至少匹配一个
  • allMatch 所有都匹配
  • noneMatch 一个都不匹配
  • findAny 返回任意一个
  • findFirst 返回第一个,类似findAny

归约

  • reduce numbers.sream().reduce(0, (a, b) -> a+b) 累加 可替换为Integer::max Integer::min Integer::sum,可能暗含装箱操作,影响性能

  • 高效:String str = strings.sream().reduce(Collectors.joining())字符串连接操作

  • reduce 自带方法 .min(Comparator.comparing()) .max()

  • 原始类型流:IntStream,DoubleStream,LongStream 还支持max,min,average等方法

    int num = users.stream().mapToInt(User::getAge).sum().orElse(1); 如果为空,会返回默认值0

  • 数值范围 rangClosed (用得少)

收集器

Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);

均使用于.collect()中

  • Collectors.joining(","); 连接字符串
  • Collectors.averagingInt();Collectors.averagingDouble();Collectors.averagingLong();平均值
  • Collectors.summarizingInt(); 求和
  • Collectors.maxBy(dishCaloriesComparator ); 最大值
  • Collectors.minBy(dishCaloriesComparator ); 最小值
  • Collectors.groupingBy(); 分组
  • Collectors.collectingAndThen() 接收两个参数,第一个参数用于 reduce 操作,而第二参数用于 map 操作

Optional

多重判断

jdk8之前的写法

public String getCity(User user)  throws Exception{
		if(user!=null){
			if(user.getAddress()!=null){
				Address address = user.getAddress();
				if(address.getCity()!=null){
					return address.getCity();
				}
			}
		}
		throw new Excpetion("取值错误"); 
	}

jdk8之后写法:

public String getCity(User user) throws Exception{
	return Optional.ofNullable(user)
				   .map(u-> u.getAddress())
				   .map(a->a.getCity())
				   .orElseThrow(()->new Exception("取指错误"));
}

简单判空

if(user!=null){
	dosomething(user);
}

改进:

 Optional.ofNullable(user)
         .ifPresent(u->{
		    dosomething(u);
		 });

if else 判断

public User getUser(User user) throws Exception{
	if(user!=null){
		String name = user.getName();
		if("zhangsan".equals(name)){
			return user;
		}
	}else{
		user = new User();
		user.setName("zhangsan");
		return user;
	}
}

改进:

public User getUser(User user) {
	return Optional.ofNullable(user)
	               .filter(u->"zhangsan".equals(u.getName()))
	               .orElseGet(()-> {
                		User user1 = new User();
                		user1.setName("zhangsan");
                		return user1;
	               });
}
<!-- 不为空时进行赋值-->
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);

CompletableFuture 异步编程

  • thenApply 接收上一个任务的结果,并且有返回值
  • thenAccept 接收上一个任务的结果,无返回值
  • thenRun 不接受上一任务的结果,无返回值
  • thenCompose 一个future依赖另外一个future返回进行组合
  • thenCombine 两个互相独立的future进行结果组合
  • CompletableFuture.allOf() 等待所有任务完成

java8之前:

ExecutorService executor = Executors.newCachedThreadPool(); 
Future<Double> future = executor.submit(new Callable<Double>() { 
    public Double call() {
        return doSomeLongComputation();  // 异步执行操作
    }}); 
    doSomethingElse();      // 异步执行同时 执行其他操作
try { 
     Double result = future.get(1, TimeUnit.SECONDS); 
} catch (ExecutionException ee) {
    // 计算抛出一个异常
} catch (InterruptedException ie) { 
    // 当前线程在等待过程中被中断
} catch (TimeoutException te) { 
    // 在Future对象完成之前超过已过期
}

java8 使用工厂方法supplyAsync创建CompletableFuture对象: 默认使用ForkJoinPool池中线程执行,也可使用重载版本,在第二个参数指定执行的线程

public Future<Double> getPriceAsync(String product) { 
    return CompletableFuture.supplyAsync(() -> calculatePrice(product), threadPoolExecutor);
}

如下几点传统方式非常繁琐:

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第 一个的结果。
  • 等待Future集合中的所有任务都完成。
  • 仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同 一个值),并返回它的结果。
  • 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
  • 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。

建议: Future获取返回值时尽量添加超时判断逻辑,否则,如果未捕获异常的话,会导致get方法永远阻塞

范例使用

注意: CompletableFuture类中的join方法和Future接口中的get有相同的含义。不同的是 join不会抛出任何检测到的异常

方式一(简单构建)

细节:使用了两个不同的stream流水线,而不是同一个处理流后面直接使用join操作,是因为流的延迟特性,必须等待上一次join返回值拿到之后才能执行下一个操作

  • 一个流:同步执行 supplyShop1->shop1.join(-->toList)->supplyShop2->shop2.join(-->toList)
  • 两个流:并行执行 supplyShop1(-->toList) -> supplyShop2(-->toList) -> shop1.join(-->toList)->shop2.join(-->toList)
List<CompletableFuture<String>> priceFutures = 
    shops.stream() .map(shop -> CompletableFuture.supplyAsync( 
        () -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)))) 
    .collect(toList());
// join不会抛出任何检测到的异常,可以是map无需try/catch让代码显得臃肿
List<String> prices = priceFutures.stream().map(CompletableFuture::join).collect(toList());

方式二(多任务构建,task2依赖task1的返回值)

选择thenCompose,而未选择thenComposeAsync方法的原因是因为thenCompose更高效一些,因为少了很多线程切换的开销

public List<String> findPrices(String product) { 
    List<CompletableFuture<String>> priceFutures = shops.stream() 
        .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor)) // 异步任务 
        .map(future -> future.thenApply(Quote::parse)) // Quote对象存在时,进行转换
        .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync( 
            () -> Discount.applyDiscount(quote), executor))) //使用另一个异步任务构造期望的Future,申请折扣
        .collect(toList()); 
        
    return priceFutures.stream().map(CompletableFuture::join).collect(toList()); 
}

原理图: 将两个异步操作进行流水线

graph LR
A(shop对象)-->|supplyAsync|B(task1 shop.getPrice)
B-->|thenApply同步操作|C(Quote::parse)
C-->|thenCompose异步操作|D(task2 applyDiscount)
D-->|join|E(price)

方式三(多任务构建,task2与task1不依赖,但需合并结果)

使用thenCombine

Future<Double> futurePriceInUSD = 
 CompletableFuture.supplyAsync(() -> shop.getPrice(product)) // task1 线程1
    .thenCombine(
        CompletableFuture.supplyAsync( 
        () -> exchangeService.getRate(Money.EUR, Money.USD)),  // task2 线程2
    (price, rate) -> price * rate      //task1返回值*task2返回值
 );

原理:

graph LR
A(shop对象)-->|supplyAsync 线程1|B(task1 shop.getPrice)
A-->|supplyAsync 线程2|C(task2 exchangeService.getRate)

B-->|thenCombine|D(price)
C-->|thenCombine|E(rate)

D-->F(price*rate)
E-->F

F-->|线程1|G(futurePriceInUSD:price*rate)
G-->|join|H(最终的price)

方式四(异常处理)

场景:异步执行任务A获取结果,如果任务A执行过程中抛出异常,则使用默认值返回

 CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "futureA result:" + s)
                .exceptionally(e -> {
                    System.out.println(e.getMessage()); //java.lang.ArithmeticException: / by zero
                    return "futureA result: 100";
                });
System.out.println(futureA.join());//futureA result: 100
// 客户端现在会收到异常,该异常接收了一个包含失败原因的Exception参数,所以,如果该方法抛出了一个运行时异常“product notavailable”,客户端就会得到像下面这样一段ExecutionException
// java.util.concurrent.ExecutionException: java.lang.RuntimeException: product not available
public Future<Double> getPriceAsync(String product) { 
    CompletableFuture<Double> futurePrice = new CompletableFuture<>(); 
    new Thread( () -> {
        try { 
            double price = calculatePrice(product);  // 耗时计算的任务
            futurePrice.complete(price); 
        } catch (Exception ex) { 
            futurePrice.completeExceptionally(ex);  // 抛出异常
        }
    }).start(); 
    return futurePrice; 
}

方式五(执行完成或者出现异常,均执行后续方法) whenComplete

执行完whenComplete->执行exceptionally 如果先执行exceptionally,则whenComplete中的异常处理不会执行,如果判断e==null的则会执行

CompletableFuture<String> futureA = CompletableFuture.
                supplyAsync(() -> "执行结果:" + (100 / 0))
                .thenApply(s -> "apply result:" + s)
                .whenComplete((s, e) -> {
                    if (s != null) {
                        System.out.println(s);//未执行
                    }
                    if (e == null) {
                        System.out.println(s);//未执行
                    } else {
                        System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
                    }
                })
                .exceptionally(e -> {
                    System.out.println("ex"+e.getMessage()); //ex:java.lang.ArithmeticException: / by zero
             return "futureA result: 100"; }); 
System.out.println(futureA.join());//futureA result: 100

响应 CompletableFuture 的 completion 事件 thenAccept

由future构成的流

public Stream<CompletableFuture<String>> findPricesStream(String product) { 
    return shops.stream() 
    .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor)) 
    .map(future -> future.thenApply(Quote::parse)) 
    .map(future -> future.thenCompose(quote -> 
            CompletableFuture.supplyAsync( 
                () -> Discount.applyDiscount(quote), executor))); 
}

thenAccept它接收CompletableFuture执行完毕后的返回值做参数。thenAcceptAsync异步接收。 一旦CompletableFuture计算得到结果,它就返回一个CompletableFuture

findPricesStream("myPhone").map(f -> f.thenAccept(System.out::println));

把构成Stream的所有CompletableFuture对象放到一个数组中,等待所有的任务执行完成

CompletableFuture[] futures = findPricesStream("myPhone") 
    .map(f -> f.thenAccept(System.out::println)) 
    .toArray(size -> new CompletableFuture[size]); 
    
CompletableFuture.allOf(futures).join(); // anyOf(futures).join();只要CompletableFuture对象数组中有任何一个执行完毕就不再等待

响应 CompletableFuture 的 completion 事件 thenRun

场景:执行任务A,任务A执行完以后,执行任务B,任务B不接受任务A的返回值(不管A有没有返回值),也无返回值 上一步执行完后执行下一步,不使用上一步的返回值

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "任务A");
futureA.thenRun(() -> System.out.println("执行任务B"));

allof

// 封装返回值
// When all the Futures are completed, call `future.join()` to get their results and collect the results in a list -
CompletableFuture<List<String>> allPageContentsFuture = allFutures.thenApply(v -> {
   return pageContentFutures.stream()
           .map(pageContentFuture -> pageContentFuture.join())
           .collect(Collectors.toList());
});

计算结果完成时的处理

不带有Async表示用同一线程继续执行,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。

  • whenComplete
  • whenCompleteAsync
  • exceptionally

转换

handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出