package com.github.java8.lambdasinaction.chap11.v1; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.stream.Collectors; import java.util.stream.Stream; import com.github.java8.lambdasinaction.chap11.ExchangeService; import com.github.java8.lambdasinaction.chap11.ExchangeService.Money; public class BestPriceFinder { private final List<Shop> shops = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"), new Shop("BuyItAll")/*, new Shop("ShopEasy")*/); private final Executor executor = Executors.newFixedThreadPool(shops.size(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } }); public List<String> findPricesSequential(String product) { return shops.stream() .map(shop -> shop.getName() + " price is " + shop.getPrice(product)) .collect(Collectors.toList()); } public List<String> findPricesParallel(String product) { return shops.parallelStream() .map(shop -> shop.getName() + " price is " + shop.getPrice(product)) .collect(Collectors.toList()); } public List<String> findPricesFuture(String product) { List<CompletableFuture<String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPrice(product), executor)) .collect(Collectors.toList()); List<String> prices = priceFutures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); return prices; } public List<String> findPricesInUSD(String product) { List<CompletableFuture<Double>> priceFutures = new ArrayList<>(); for (Shop shop : shops) { // Start of Listing 10.20. // Only the type of futurePriceInUSD has been changed to // CompletableFuture so that it is compatible with the // CompletableFuture::join operation below. CompletableFuture<Double> futurePriceInUSD = CompletableFuture.supplyAsync(() -> shop.getPrice(product)) .thenCombine( CompletableFuture.supplyAsync( () -> ExchangeService.getRate(Money.EUR, Money.USD)), (price, rate) -> price * rate ); priceFutures.add(futurePriceInUSD); } // Drawback: The shop is not accessible anymore outside the loop, // so the getName() call below has been commented out. List<String> prices = priceFutures .stream() .map(CompletableFuture::join) .map(price -> /*shop.getName() +*/ " price is " + price) .collect(Collectors.toList()); return prices; } public List<String> findPricesInUSDJava7(String product) { ExecutorService executor = Executors.newCachedThreadPool(); List<Future<Double>> priceFutures = new ArrayList<>(); for (Shop shop : shops) { final Future<Double> futureRate = executor.submit(new Callable<Double>() { public Double call() { return ExchangeService.getRate(Money.EUR, Money.USD); } }); Future<Double> futurePriceInUSD = executor.submit(new Callable<Double>() { public Double call() { try { double priceInEUR = shop.getPrice(product); return priceInEUR * futureRate.get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e.getMessage(), e); } } }); priceFutures.add(futurePriceInUSD); } List<String> prices = new ArrayList<>(); for (Future<Double> priceFuture : priceFutures) { try { prices.add(/*shop.getName() +*/ " price is " + priceFuture.get()); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } } return prices; } public List<String> findPricesInUSD2(String product) { List<CompletableFuture<String>> priceFutures = new ArrayList<>(); for (Shop shop : shops) { // Here, an extra operation has been added so that the shop name // is retrieved within the loop. As a result, we now deal with // CompletableFuture<String> instances. CompletableFuture<String> futurePriceInUSD = CompletableFuture.supplyAsync(() -> shop.getPrice(product)) .thenCombine( CompletableFuture.supplyAsync( () -> ExchangeService.getRate(Money.EUR, Money.USD)), (price, rate) -> price * rate ).thenApply(price -> shop.getName() + " price is " + price); priceFutures.add(futurePriceInUSD); } List<String> prices = priceFutures .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); return prices; } public List<String> findPricesInUSD3(String product) { // Here, the for loop has been replaced by a mapping function... Stream<CompletableFuture<String>> priceFuturesStream = shops .stream() .map(shop -> CompletableFuture .supplyAsync(() -> shop.getPrice(product)) .thenCombine( CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD)), (price, rate) -> price * rate) .thenApply(price -> shop.getName() + " price is " + price)); // However, we should gather the CompletableFutures into a List so that the asynchronous // operations are triggered before being "joined." List<CompletableFuture<String>> priceFutures = priceFuturesStream.collect(Collectors.toList()); List<String> prices = priceFutures .stream() .map(CompletableFuture::join) .collect(Collectors.toList()); return prices; } }