SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine(コードジン) DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング

ラムダ式でStrategyパターンで実装されたコードをシンプルにする ~ そこから見えてくるストラテジオブジェクトの本質

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング 第2回

ログ出力(ラムダ式バージョン)

 それでは次に、Java SE 8のラムダ式を用いてログ出力プログラムを書き直してみましょう。

 リスト2の中で、Strategyの役割を担う TimestampFormatter インタフェースは、format メソッドを唯一の抽象メソッドとして持つ関数型インタフェースです。したがって、TimestampFormatter インタフェースのインスタンスは、EpochFormatter クラスや LocalDateTimeFormatter クラスのような ConcreteStrategy の役割を担うクラスを宣言することなく、ラムダ式によって直接生成できるはずです。

 以上の方針に基づいて、ラムダ式を用いてプログラムを設計した場合のクラス図は、図6のようになります。

図6:ログ出力プログラムのシーケンス図(ラムダ式)
図6:ログ出力プログラムのシーケンス図(ラムダ式)

 

 ラムダ式を用いてリスト2を書き換えたプログラムを、リスト3に掲げます。リスト2からの変更点は「★」印で示しています。

リスト3:LambdaLoggerClient.java ~ ラムダ式を用いたログ出力プログラム
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/** ロガー. */
class Logger {

    /** ロガーの名前. */
    private final String name;

    /** 時刻を文字列化するストラテジ. */
    private final TimestampFormatter formatter;

    Logger(String name, TimestampFormatter formatter) {
        this.name = name;
        this.formatter = formatter;
    }

    /** ログを出力する. */
    public void log(String message) {
        System.err.printf("%s [%s] %s%n", now(), this.name, message);
    }

    /** 現在時刻の文字列表現. */
    private String now() {
        return this.formatter.format(Instant.now());
    }

    /** 時刻を文字列化するストラテジのインタフェース. ★関数型インタフェースであることを明示. */
    @FunctionalInterface
    interface TimestampFormatter {
        String format(Instant instant);
    }

}

public class LambdaLoggerClient {

    public static void main(String[] args) {
        // 時刻をエポック時間で表示するロガー (★ラムダ式を使用)
        Logger epochLogger = new Logger("epoch", instant -> {
            BigDecimal milli = BigDecimal.valueOf(instant.toEpochMilli());
            BigDecimal sec = milli.divide(BigDecimal.valueOf(1000)).setScale(3);
            return sec.toPlainString();
        });
        epochLogger.log("Hey!");
        epochLogger.log("Ho!");
        epochLogger.log("Let's go!");

        // 日本時間のタイムゾーン
        ZoneId jst = ZoneId.of("Asia/Tokyo");
        // 時刻を日本時間で表示するロガー (★ラムダ式を使用)
        Logger jstLogger = new Logger("jst", instant -> LocalDateTime.ofInstant(instant, jst).toString());
        jstLogger.log("Hey!");
        jstLogger.log("Ho!");
        jstLogger.log("Let's go!");
    }
}

 リスト3のプログラムの実行結果は次のとおりです。実行時刻を使っているため個々の値は異なりますが、リスト2の実行結果と同じです。

1420440856.809 [epoch] Hey!
1420440856.859 [epoch] Ho!
1420440856.861 [epoch] Let's go!
2015-01-05T15:54:16.891 [jst] Hey!
2015-01-05T15:54:16.893 [jst] Ho!
2015-01-05T15:54:16.895 [jst] Let's go!

 リスト3でラムダ式を用いている箇所は、main メソッド内でストラテジオブジェクトを Logger クラスのコンストラクタに渡しているところ、たとえば instant -> LocalDateTime.ofInstant(instant, jst).toString() の部分です。Logger クラスのコンストラクタの第2引数は TimestampFormatter インタフェース型として宣言されているので、これらのラムダ式は TimestampFormatter インタフェース型のインスタンスを生成します。

 リスト2では、ストラテジオブジェクトのクラスを宣言して計算を記述するところと、インスタンスを生成して Logger クラスのコンストラクタに渡すところが離れていました。これに対してリスト3では、インスタンスを生成するその場に計算が記述できており、簡潔になっていることが分かります。また、単純に行数を比較しても、80行から51行へと、29行削減できています。

 場合によっては、時刻を文字列に変換する計算を、コンストラクタに渡すその場で記述するのではなく、複数の箇所で使い回したいことがあるかもしれません。このような課題は、リスト4のようにラムダ式の結果を定数に保存したり( EPOCH_FORMATTER フィールド)、ラムダ式の結果を戻すメソッドを作ったり( localDateTimeFormatter メソッド)することによって解決できます。

リスト4: ReusableLambdaLoggerClient.java
public class ReusableLambdaLoggerClient {

    /** 時刻をエポック時間の文字列に変換するストラテジオブジェクト. */
    private static final TimestampFormatter EPOCH_FORMATTER = instant -> {
        BigDecimal milli = BigDecimal.valueOf(instant.toEpochMilli());
        BigDecimal sec = milli.divide(BigDecimal.valueOf(1000)).setScale(3);
        return sec.toPlainString();
    };

    /** 時刻をyyyy-MM-dd'T'HH:mm:ss.SSS形式の文字列に変換するストラテジオブジェクトを戻す. */
    private static TimestampFormatter localDateTimeFormatter(ZoneId zoneId) {
        return instant -> LocalDateTime.ofInstant(instant, zoneId).toString());
    }

    public static void main(String[] args) {
        // 時刻をエポック時間で表示するロガー
        Logger epochLogger = new Logger("epoch", EPOCH_FORMATTER);
        epochLogger.log("Hey!");
        epochLogger.log("Ho!");
        epochLogger.log("Let's go!");

        // 日本時間のタイムゾーン
        ZoneId jst = ZoneId.of("Asia/Tokyo");
        // 時刻を日本時間で表示するロガー
        Logger jstLogger = new Logger("jst", localDateTimeFormatter(jst));
        jstLogger.log("Hey!");
        jstLogger.log("Ho!");
        jstLogger.log("Let's go!");
    }
}

次のページ
考察:ラムダ式によって「計算を表すオブジェクト」という本質が見えてくる

この記事は参考になりましたか?

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング連載記事一覧

もっと読む

この記事の著者

宮川 拓(ミヤカワ タク)

日本Javaユーザーグループ幹事。東京のシステムインテグレータに勤務。Java VM上で動作する言語である「Kink」を開発中。相撲とアメリカ文学とスコティッシュポップを愛する。 ・ブログ: http://d.hatena.ne.jp/miyakawa_taku/ ・Twitter: @miyakawa_taku

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8401 2015/01/20 14:00

おすすめ

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー