ログ出力(ラムダ式バージョン)
それでは次に、Java SE 8のラムダ式を用いてログ出力プログラムを書き直してみましょう。
リスト2の中で、Strategyの役割を担う TimestampFormatter インタフェースは、format メソッドを唯一の抽象メソッドとして持つ関数型インタフェースです。したがって、TimestampFormatter インタフェースのインスタンスは、EpochFormatter クラスや LocalDateTimeFormatter クラスのような ConcreteStrategy の役割を担うクラスを宣言することなく、ラムダ式によって直接生成できるはずです。
以上の方針に基づいて、ラムダ式を用いてプログラムを設計した場合のクラス図は、図6のようになります。
ラムダ式を用いてリスト2を書き換えたプログラムを、リスト3に掲げます。リスト2からの変更点は「★」印で示しています。
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 メソッド)することによって解決できます。
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!");
}
}

