第6章 並行処理の機能強化 : 問題 10 : CompletableFuture の合成
問題
ユーザーにURLを問い合わせて、そのURLのWebページを読み込み、全てのリンクを表示するプログラムを作成せよ
- 各ステップで
CompletableFuture
を使用せよ CompletableFuture
のget
メソッドを呼び出さないこと
準備
各ステップの処理を(Future
を使わないで)普通に実装する
■ ユーザーにURLを問い合わせてコンソールから入力
// 標準入力 private static Scanner stdin = new Scanner(System.in); // ユーザーにURLを問い合わせてコンソールから入力 public static URL getURLInput(String prompt) { for (;;) { System.out.print(prompt + ": "); try { return new URL(stdin.nextLine()); } catch (MalformedURLException ex) { // 間違った URL なら再入力 } } }
■ 指定された URL の Web ページを読み込む
public static String blockingReadPage(URL url) { StringBuilder builder = new StringBuilder(); // 文字コードは UTF-8 に固定する try (BufferedReader reader = new BufferedReader( new InputStreamReader(url.openStream(), StandardCharsets.UTF_8) )) { String line; while ((line = reader.readLine()) != null) { builder.append(line); builder.append('\n'); } } catch (IOException ex) { // CompletableFuture を見越してチェック例外はスローしない throw new RuntimeException(ex); } return builder.toString(); }
■ HTMLのリンク一覧の取得
本当は JavaFX の WebKit を使いたいが、WebEngine
は JavaFX のスレッドでないと動作しない
しょうがないので Swing のHTMLパーサーを使う(トホホ…)
public class Paser extends HTMLEditorKit.ParserCallback { // リンク一覧 private List<String> links = new ArrayList<String>(); // anchor タグからリンク先を収集 public void handleStartTag(HTML.Tag tag, MutableAttributeSet attrs, int pos) { if (tag.equals(HTML.Tag.A)) { links.add((String)attrs.getAttribute(HTML.Attribute.HREF)); } super.handleStartTag(tag, attrs, pos); } // HTMLのリンク一覧の取得 public static List<String> getLinks(String contents) { Paser callback = new Paser(); ParserDelegator delegator = new ParserDelegator(); try { delegator.parse(new StringReader(contents), callback, true); } catch (IOException ex) { // あり得ないが… throw new RuntimeException(ex); } return callback.links; } }
解答
CompletableFuture
を使わないで、シーケンシャルに実行するのは
■ シーケンシャルに実行
URL url = getURLInput("URLを入力してください");
String contents = blockingReadPage(url);
List<String> links = Paser.getLinks(contents);
System.out.println(links);
となる
■ CompletableFuture
を使う
CompletableFuture .supplyAsync(() -> getURLInput("URLを入力してください")) .thenApplyAsync(Exercise10::blockingReadPage) .thenApplyAsync(Paser::getLinks) .thenAcceptAsync(System.out::println); // タスクの処理が終わる前に終了しないようにする(特に getURLInput メソッド)おまじない ForkJoinPool.commonPool().awaitQuiescence(10, TimeUnit.SECONDS);