CUBは子供の白熊

Java SE 8 実践プログラミングの練習問題を解く

第6章 並行処理の機能強化 : 問題 10 : CompletableFuture の合成

問題

ユーザーにURLを問い合わせて、そのURLのWebページを読み込み、全てのリンクを表示するプログラムを作成せよ

  • 各ステップでCompletableFutureを使用せよ
  • CompletableFuturegetメソッドを呼び出さないこと

準備

各ステップの処理を(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のリンク一覧の取得

本当は JavaFXWebKit を使いたいが、WebEngineJavaFX のスレッドでないと動作しない
しょうがないので 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);