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方法只是用来处理正常值,因此一旦有异常就会抛出