#概要
黒魔術ライブラリことLombokについての解説です。公式ドキュメントからの和訳と多分な意訳によって構成されています。
すでにこちらの”JavaでAndroid開発をするなら絶対に導入したいLombok - 超戦士が秘めたる13のパワー[劇場版]”で解説がありますが、当記事では各パラメータやオプションの解説も行っていきます。そういった部分も把握して使いこなしたい、と言った場合に参考にしていただければと思います。
Experimental features(実験的な機能)はバージョンアップによって削除される可能性が高いため解説は行いません。Lombokのアップデートによってメインパッケージへと移動した場合は追記を行う予定です。
かなり長くなるので見出しをご活用ください。
Lombokは、注釈(アノテーション)処理によってゲッター/セッターの自動生成、型推論、Nullチェックなどのコードを生成してくれるライブラリです。
EclipseならLombok側が自動で使えるようにしてくれますし、IntelliJ IDEAにもプラグインがあるので、IDEの強力なコーディングサポート機能と合わせて利用できます。インストールについてはここでは省略します。他サイトさんをご覧ください。
Lombok v1.16.2
こちらのLombok feature overviewをもとに書かれています。
#基本機能解説
val list = new ArrayList<String>();
list.add("Hello");
list.add("World!");
for (val str : list) {
System.out.println(str);
}
右辺の初期化式から型を推論します。final
も付加されます。コンパイル時はfinal <クラス名>
の形となります。ローカル変数と拡張for文(foreach)にのみ使用可能で、フィールドで使うことはできません。
この機能は現在のNetBeansでは動作しません。Lombokプロジェクトはこの問題の解決に取り組んでいます。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーで表示。
Lombokを用いた記法
public class Person {
private final String name;
public Person(@NonNull String name) {
this.name = name;
}
}
変換後
public class Person {
private final String name;
public Person(@NonNull String name) {
if (name == null) {
throw new NullPointerException("person");
}
this.name = name;
}
}
コンストラクタまたはメソッドのパラメータに指定することで、そのパラメータのnullチェック構文を生成します。フィールドに付加すれば、後述の@Getter
/@Setter
アノテーションで生成されたセッターにも適用できます。すでにnullチェックがある場合追加の生成は行いません。
フィールド、メソッド、パラメータ、ローカル変数にて使用可能。
値:NullPointerException
IllegalArgumentException
デフォルト:NullPointerException
スローする例外を指定します。IllegalArgumentExceptionを指定すると、FIELD_NAME is null
という形式でエラーメッセージが設定されます。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーで表示。
@Cleanup Scanner scanner = new Scanner(System.in);
変数に付加することで、現在のスコープを抜けた際にクリーンアップするコードを生成します。上記のScanner
クラスではclose()
が呼び出されます。クリーンアップ処理はfinally内でnullチェックとともに実行されます。メソッドが1つ以上の引数を取る場合は実行できません。
@Cleanup("dispose")
クリーンアップを行うメソッド名を指定できます。デフォルトは"close"
です。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーで表示。
public class Person {
@Getter @Setter private String name;
@Getter @Setter(AccessLevel.PROTECTED) int age;
}
フィールドに付加することで、そのフィールドのゲッター/セッターを生成します。
@Getter
はgetXXX
(booleanの場合はisXXX
)という名前で生成し、単純にそのフィールドを返します。@Setter
はsetXXX
という名前で生成し、void
を返して1つのパラメータを取りフィールドにセットします。
クラスに付加すると、クラス内の非staticなすべてのフィールドに適用されます。
@Getter(AccessLevel.PROTECTED)
アクセスレベルを指定します。明示的に指定しなかった場合はlombok.AccessLevel.PUBLIC
が使われ、アクセスレベルはpublic
になります。lombok.AccessLevel.NONE
を指定することで、クラスに指定されたすべての@Getter
/@Setter
/@Data
(後ほど紹介)による生成を無効にすることができます。
@Getter(lazy=true)
@Getter
専用。後述の@Getter(lazy=true)
を参照してください。
値:true
false
デフォルト:false
boolean
フィールドのゲッターのプレフィクスをisではなくデフォルトのgetにします。例えば、フィールドclosed
のゲッター名はgetClosed
になります。
値:warning
error
デフォルト値:なし
@Setter
の箇所を警告かエラーで表示。
値:warning
error
デフォルト値:なし
@Getter
の箇所を警告かエラーで表示。
@Getter @Setter @ToString
public class Person {
private String name;
private int age;
}
public Main() {
Person person = new Person();
person.setName("Getaji");
person.setAge(7);
System.out.println(person.toString());
}
出力
Main.Person(name=Getaji, age=7)
toString()
メソッドの実装を生成します。フィールドをカンマ区切りにし、クラス名を付加します。
@ToString(includeFieldNames=false)
フィールド名を含むか設定します。デフォルトはtrue
です。
@ToString(exclude={"realAge"})
除外するフィールド名を指定します。上記の例では実際の年齢を表すrealAge
フィールドが出力されなくなります。of
とは競合するため併用できません。
@ToString(of={"name", "age"})
出力するフィールド名を設定します。これ以外のフィールドは出力されません。exclude
とは競合するため併用できません。
@ToString(callSuper=true)
親クラスのtoString()
を含むか設定します。デフォルトはfalseです。
@ToString(doNotUseGetters=true)
ゲッターが存在する場合にそれを使わずフィールドを直接参照するか設定します。デフォルトはfalse
です。
値:true
false
デフォルト:true
フィールド名を含むか設定します。前述のincludeFieldNames
パラメータの明示的設定はこれを上書きします。
値:true
false
デフォルト:false
ゲッターが存在する場合にそれを使わずフィールドを直接参照するか設定します。前述のdoNotUseGetters
パラメータの明示的設定はこれを上書きします。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーで表示。
例では簡略化のためGetter/Setterは省略
Lombokを用いた記法
@EqualsAndHashCode
public class Person {
private String name;
private int age;
}
変換後
public class Person {
private String name;
private int age;
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Person)) return false;
final Person other = (Person) o;
if (!other.canEqual((Object) this)) return false;
final Object this$name = this.name;
final Object other$name = other.name;
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
if (this.age != other.age) return false;
return true;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.name;
result = result * PRIME + ($name == null ? 0 : $name.hashCode());
result = result * PRIME + this.age;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
}
equals()
とhashCode()
の実装を生成します。非static、非transientな全てのフィールドを使用します。後述のパラメータで、含むフィールド・含まないフィールドを設定可能です。
equals()
をオーバーライドしているクラスでhashCode()
をオーバーライドしない場合は、hashCodeの一般契約を破ることになります。両者が正しく設定されていることで、hashCodeを扱うHashMap
、HashSet
などのコレクションが正しく動作します。
@EqualsAndHashCode(exclude={"cache"})
除外するフィールド名を指定します。of
とは競合するため併用できません。
@EqualsAndHashCode(of={"name", "age"})
使用するフィールド名を設定します。これ以外のフィールドは使われません。exclude
とは競合するため併用できません。
@EqualsAndHashCode(callSuper=true)
スーパークラスのequals()
とhashCode()
を使うか設定します。デフォルトはfalseです。super.equals(object)
がfalseを返した場合、生成されたequals()
はfalseを返します。
@EqualsAndHashCode(doNotUseGetters=true)
ゲッターが存在する場合にそれを使わずフィールドを直接参照するか設定します。デフォルトはfalseです。
@EqualsAndHashCode(onParam=@__(@Dummy))
生成したパラメータに付加するアノテーションを指定します。
値:true
false
デフォルト:false
ゲッターが存在する場合にそれを使わずフィールドを直接参照するか設定します。前述のdoNotUseGetters
パラメータの明示的設定はこれを上書きします。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
これらのアノテーションで生成されたコンストラクタは、明示的に宣言したコンストラクタと競合します。
@NoArgsConstructor
public class Person {
private String name;
private int age;
}
パラメータなしのコンストラクタを生成します。finalフィールドがある場合はコンパイルエラーになります。Javaはコンストラクタが明示的に宣言されていないと引数を取らないデフォルトコンストラクタを生成するため、この例ではコードは生成されません。
with lombok
@RequiredArgsConstructor
public class Person {
private final String name;
private int age;
}
vanilla java
public class Person {
private final String name;
private int age;
@java.beans.ConstructorProperties({"name"})
public Person(String name) {
this.name = name;
}
}
finalフィールドを初期化するためにそれらをパラメータとして要求するコンストラクタを生成します。@NonNull
が付加されたフィールドは同時にNullチェックも行われます。@NonNull
が付加されたフィールドを対象としたパラメータにnullが渡された場合、NullPointerExceptionをスローします。
with lombok
@AllArgsConstructor
public class Person {
private final String name;
private int age;
}
vanilla java
public class Person {
private final String name;
private int age;
@java.beans.ConstructorProperties({"name", "age"})
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
すべてのフィールドに対するパラメータを取るコンストラクタを生成します。@NonNull
が付加されたフィールドは同時にNullチェックも行われます。
@AllArgsConstructor(staticName="of")
指定された名前でstaticファクトリメソッドを生成します。対応するコンストラクタはprivateになります。デフォルトは空文字で、staticファクトリメソッドは生成されません。
@AllArgsConstructor(onConstructor=@__(@Dummy))
生成したコンストラクタに付加するアノテーションを指定します。
@AllArgsConstructor(suppressConstructorProperties=true)
コンストラクタに付加されるjava.beans.ConstructorProperties
アノテーションの生成を抑止するかどうかを設定します。このアノテーションはJava1.6からのもので、それ以前ではコンパイルに失敗します。JVM上で動作する場合は注釈は無視されます。デフォルトはfalseです。
値:true
false
デフォルト:false
コンストラクタに付加されるjava.beans.ConstructorProperties
アノテーションの生成を抑止するかどうかを設定します。
値:warning
error
デフォルト値:なし
指定した種類のコンストラクタ生成アノテーションの使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
すべてのコンストラクタ生成アノテーションの使用箇所を警告かエラーでフラグ立て。
@Data
public class Person {
private final String name;
private int age;
}
@ToString
、@EqualsAndHashCode
、すべてのフィールドへの@Getter
、非finalフィールドへの@Setter
、@RequiredArgsConstructor
を束ねる便利アノテーションです。
@Dataには個別のアノテーションへのパラメータを渡すことはできませんが、明示的にそれら個別のアノテーションを宣言しパラメータを渡すことで適用されます。
すべてのゲッター/セッターはpublicです。これらのアクセスレベルの変更、生成の抑止を行う場合はそれぞれのフィールドにアノテーションを付加し、適切なlombok.AccessLevel
を設定してください。
transient
が付加されたフィールドはhashCode()
とequals()
の参照からは除外されます。static
が付加されたフィールドはすべての処理をスキップします。
すでに同じ名前とパラメータ数を持つコンストラクタやメソッドが生成されている場合は追加で生成されません。これを回避(Lombokから隠蔽)する場合は、それぞれに@lombok.experimental.Tolerate
を付加することで実現できます。
@Data(staticConstructor = "of")
指定された名前でstaticファクトリメソッドを生成します。対応するコンストラクタはprivateになります。総称型もちゃんと処理してくれます。デフォルトは空文字で、staticファクトリメソッドは生成されません。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
@Value
public class Person {
String name;
int age;
}
イミュータブル(不変)な@Data
です。すべてのフィールドはprivate final
で、セッターが生成されません。クラスもデフォルトではfinalです。@Dataと同じく、すでにコンストラクタやゲッターなどが明示的に宣言されている場合は追加の生成を行いません。それらをLombokから隠したい(隠蔽する)場合も、同じくそれぞれに@lombok.experimental.Tolerate
を付加することで実現できます。
フィールドは、デフォルトアクセス(パッケージプライベート)の場合にprivateになり、publicなどを明示的に指定した場合はそれらが優先されます。また、@PackagePrivate
や@NonFinal
アノテーションなどを使用することでデフォルトの動作を上書きできます。
@Value(staticConstructor = "of")
指定された名前でstaticファクトリメソッドを生成します。対応するコンストラクタはprivateになります。@Dataの同名パラメータと同じです。デフォルトは空文字で、staticファクトリメソッドは生成されません。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
throws節で宣言しなくても検査例外をスローできることができるようになります。
class Example {
void sneakyThrow(Throwable t) {
Example.<RuntimeException>sneakyThrow0(t);
}
<T extends Throwable> void sneakyThrow0(Throwable t) throws T {
throw (T)t;
}
}
- sneakyThrow0は総称型引数としてRuntimeExceptionを渡されたため、非検査例外としてスロー。そのため、sneakyThrowはthrows節に書く必要がない。まだコンパイル時なのでキャストはしないため、ClassCastExceptionは発生しない。このコンパイルが成功したあとで型情報が消える。
-
例外がThrowableとしてsneakyThrow0に渡される。
-
総称型の型情報が消えているため、sneakyThrow0はThrowableとしてキャスト。
(Throwable) t
なので実質キャストは行われず、そのままスロー。 -
JVM上では検査/非検査例外の区別はないので、コンパイル時に非検査例外として例外を扱っていたsneakyThrowも問題なく通過。
対象の例外クラスを指定します。指定された例外(とその子クラス)以外はsneakyThrowされません。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
@Synchronized
public void apply() {
// do something
}
静的/インスタンスメソッドに付加することで、synchronizedブロックを生成します。synchronized修飾子と同様に機能しますが、@Synchronizedはprivateで$lock
という名前のフィールドををロックします。存在しない場合は生成します。静的メソッドの場合は$LOCK
となります。
自分で同じように実装した場合は追加の生成は行われません。
ロックするフィールド名を指定します。この場合はフィールドの生成は行われず、明示的に宣言する必要があります。thisやclassは競合や厄介なスレッド関連のバグを生む副作用を孕んでいるため、使用できません。デフォルトは空文字で、$lock
が自動生成されます。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
公式から拝借
@Getter(lazy=true) private final double[] cached = expensive();
private double[] expensive() {
double[] result = new double[1000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.asin(i);
}
return result;
}
}
生成したGetterに、初回呼び出し時の値をキャッシュし、次回にそのキャッシュを返す機能を追加生成します。
対象のフィールドは、まず原子的(スレッドセーフ)な更新が可能な参照クラス:java.util.concurrent.AtomicReference<java.lang.Object>
に置き換えられ、ゲッターメソッドが生成されます。
実行時、まずAtomicReferenceから値を取得します。nullの場合はexpensive()
メソッド(予め明示的に実装してある必要がある)で初期化を行って値を求め、その結果がnullでない場合にAtomicReferenceフィールドにセットされます。最終的にnullでない値がセットされた場合は戻り型にキャストを行いその値を、nullの場合はnullを返して次回の呼び出し時に再度取得処理を行います。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
クラスに付加することで、任意のLoggerのフィールドを生成します。使用できるLoggerは以下のとおりです。サンプルコードではLogExampleクラスに記述しています。
private static final org.apache.commons.logging.Log log =
org.apache.commons.logging.LogFactory.getLog(LogExample.class);
private static final java.util.logging.Logger log =
java.util.logging.Logger.getLogger(LogExample.class.getName());
private static final org.apache.log4j.Logger log =
org.apache.log4j.Logger.getLogger(LogExample.class);
private static final org.apache.logging.log4j.Logger log =
org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(LogExample.class);
private static final org.slf4j.ext.XLogger log =
org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
@Log4j2(topic = "DebugLog")
トピック(もしくは名前)を設定します。デフォルトは空文字で、アノテーションを付加したクラスが使われます。
値:文字列
デフォルト:log
生成するフィールドの名前を設定します。
値:true
false
デフォルト:true
生成するフィールドをstatiにするか設定します。falseにするとインスタンスフィールドとなります。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.apachecommons.CommonsLog
の使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.java.Log
の使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.log4j.Log4j
の使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.log4j.Log4j2
の使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.slf4j.Slf4j
の使用箇所を警告かエラーでフラグ立て。
値:warning
error
デフォルト値:なし
@lombok.extern.slf4j.XSlf4j
の使用箇所を警告かエラーでフラグ立て。
#experimental features
experimental featuresは実験的な機能です。通常のLombokインストールで使用できますが、主な機能としてのサポートは行われていません。
-
主要機能と同等のテストは行われていません。
-
主要機能と同等の迅速なバグフィクスは行われません。
-
問題を解決するための手段が劇的な改善をもたらす場合は、APIに変更がかかる可能性があります。
-
サポートが困難か、十分な決まり文句がつかない場合、APIは削除される可能性があります。
正式コミュニティからのフィードバックや洗練されており柔軟な機能であると確認された場合は、主要機能に移動します。
要するにバグがあったり黒魔術過ぎたりする機能です。重要なプロジェクトでの使用は控えたほうがいいでしょう。
この機能はまもなく主要機能に移動します。
@Data
@Builder
public class Person {
private String name;
private int age;
private String address;
}
usage
Person person = Person.builder()
.name("Getaji").age(17).address("getaji.com")
.build();
Builder生成を行うAPIです。複雑なインスタンス生成をBuilderパターンを以って容易に行うことができます。
Builderインスタンスの生成を行うbuild()
はstaticメソッドとして生成されます。@Builderはクラス、コンストラクタ、静的メソッドに配置することができます。クラスとコンストラクタは最も一般的なユースケースですが、ここでは最も簡単な静的メソッドユースケースを説明します。
@Builderによって、以下の7つが生成されます。
-
静的メソッドと同じ型の引数を持つ
~Builder
という名前の静的クラス -
Builder内に、各パラメータに対応する1つずつのprivate 非static finalのフィールド
-
Builder内に、package privateで引数を取らないコンストラクタ
-
Builder内に、各パラメータに対してセッターライクに値をセットするメソッド。メソッド名はパラメータ名と同じ。セッター呼び出しの連鎖(メソッドチェイン)ができるようにBuilder自身のインスタンスを返す
-
Builder内に、パラメータを渡して静的メソッドを呼び出す
build()
メソッド。対象の静的メソッドと同じ型を返す -
Builder内に、
toString()
の実装 -
ターゲットのクラス内に、Builderの新しいインスタンスを返す
builder()
メソッド
上記のリストアップされた要素は、すでに存在している場合生成はスキップされます。これにはBuilder自体も含まれます。まだ生成されてない要素がある場合は、それらの要素の生成を行います。ただし、他のLombokアノテーションによる生成は行われないことがあります。例えば、@EqualsAndHashCodeを使うことはできません。
静的メソッドに付加された場合は、そのメソッドの戻り型を構築するようなBuilderを生成します。
@AllArgsConstructor(access = AccessLevel.PACKAGE)
と@Builder
をクラスに付加したものは、全ての値を取るpakage privateなコンストラクタに@Builderを付加したのと同等です。
Builderの名前は、@Builderが付加されたクラス名、コンストラクタやメソッドの戻り型の名前が付加されます。voidを返す静的メソッドに付加されると、Builderの名前はVoidBuilder
になります。
@Builder(builderMethodName = "HelloWorld")
Builderインスタンスを返すメソッドの名前を設定します。デフォルトは"builder"です。
@Builder(buildMethodName = "execute")
Builderで対象のクラスインスタンスを生成するメソッドの名前を設定します。デフォルトは"build"です。
@Builder(builderClassName = "HelloWorldBuilder")
Builderのクラス名を設定します。デフォルトは空文字で、前述の方法で命名されます。
@Builder(fluent = false)
Builderのセッターメソッドの名前にget/setを付加しないようにするか設定します。デフォルトはtrueです。
@Builder(chain = false)
呼び出し側でメソッドチェインを行うために、Builderのセッターメソッドで自身(Builder)のインスタンスを返すか設定します。デフォルトはtrueです。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
@Accessors
@Data
public class Person {
private String name;
private int age;
}
Lombokがゲッター・セッターをどのように生成しどのように見せるかを設定することができます。
デフォルトでは、ゲッターとセッターはBean仕様に従います。例えばpepperという名前の変数のゲッターはgetPepperです。しかし、もしかしたらよりよいAPIにするためにBean仕様を破るほうが好ましいかもしれません。@Accessorはこれを実現します。
@Accessors(fluent = true)
ゲッター・セッターの名前にget/setを付加しないようにするか設定します。デフォルトはfalseです。
@Accessors(chain = false)
呼び出し側でメソッドチェインを行うために、セッターで自身のインスタンスを返すか設定します。デフォルトはtrueです。
@Accessors(prefix = {"f", "x"})
フィールドの接頭辞を設定します。フィールドは、設定されたこれらの接頭辞のいずれかをつける必要があります。各フィールド名は順番にこれらの接頭辞と比較され、一致した場合はゲッター・セッター名のベース名用に接頭辞の部分が取り除かれます。フィールド名は小文字にすることはできません。例えば上記の例ではxpepprは一致しませんが、xPepperは一致します。空文字を含むこともでき、すべての変数名と一致します。
値:true
false
デフォルト:false
trueの場合、@Accessorsを持っていないいずれかのクラスか、またはその@Accessorsアノテーションが明示的なchainパラメータを持っていない場合、@Accessor(chain = true)
が存在するかのように機能します。
値:true
false
デフォルト:false
trueの場合、@Accessorsを持っていないいずれかのクラスか、またはその@Accessorsアノテーションが明示的なfluentパラメータを持っていない場合、@Accessor(fluent = true)
が存在するかのように機能します。
値:リスト(+=で追加、-=で削除)
デフォルト:空
@Accessorsを持っていないいずれかのクラスか、またはその@Accessorsアノテーションが明示的なprefixパラメータを持っていない場合、@Accessor(prefix = {設定に含まれているリスト})
が存在するかのように機能します。
値:warning
error
デフォルト値:なし
使用箇所を警告かエラーでフラグ立て。
拡張メソッドのようなものを使えるようにします。筆者が魅力を感じないという理由でこのセクションはスキップされます。ゆるして。そのうち書くかもしれない。