最近、Javaコードを書くことを想像できないツールやライブラリがたくさんあります。伝統的に、 Google Guava または ジョーダタイム (少なくともJava 8以前の時代では)手元の特定のドメインに関係なく、ほとんどの場合、プロジェクトに投げ込む依存関係の1つです。
ロンボク 典型的なライブラリ/フレームワークユーティリティではありませんが、確かに私のPOMやGradleビルドでもその位置に値します。 Lombokはかなり前から存在しており(2009年に最初にリリースされました)、それ以来かなり成熟しています。ただし、これはもっと注意を払う価値があると常に感じていました。これは、Javaの自然な冗長性に対処するためのすばらしい方法です。
この投稿では、Lombokがこのような便利なツールである理由を探ります。
Javaには、注目に値するソフトウェアであるJVM自体以外にも多くのことがあります。 Javaは成熟していてパフォーマンスが高く、その周りのコミュニティとエコシステムは巨大で活気があります。
ただし、プログラミング言語として、Javaには独自の特異性と、かなり冗長にすることができる設計上の選択肢があります。 Java開発者が頻繁に使用する必要のあるいくつかの構造とクラスパターンを追加すると、制約やフレームワークの規則に準拠する以外に、実際の価値をほとんどまたはまったくもたらさないコード行が多数発生することがよくあります。
ここでLombokが活躍します。これにより、作成する必要のある「ボイラープレート」コードの量を大幅に削減できます。 Lombokのクリエイターは非常に賢い人で、確かにユーモアを好みます。見逃すことはできません。 このイントロ 彼らは過去の会議で作りました!
ロンボクがその魔法をどのように行うかといくつかの使用例を見てみましょう。
Lombokは、コンパイル時にクラスにコードを「追加」するアノテーションプロセッサとして機能します。 注釈処理 は、バージョン5でJavaコンパイラに追加された機能です。ユーザーがアノテーションプロセッサ(自分で作成したもの、またはLombokなどのサードパーティの依存関係を介して作成したもの)をビルドクラスパスに配置できるという考え方です。次に、コンパイルプロセスが進行しているときに、コンパイラがアノテーションを見つけるたびに、「ねえ、この@Annotationに興味があるクラスパスの誰か?」と尋ねます。手を挙げているプロセッサの場合、コンパイラは制御をプロセッサに転送し、コンパイルコンテキストを処理します。
おそらく、注釈プロセッサの最も一般的なケースは、新しいソースファイルを生成するか、ある種のコンパイル時チェックを実行することです。
Lombokは、実際にはこれらのカテゴリに分類されません。Lombokは、コードを表すために使用されるコンパイラデータ構造を変更します。つまり、 抽象構文木(AST) 。コンパイラのASTを変更することにより、Lombokは最終的なバイトコード生成自体を間接的に変更しています。
この変わった、かなり煩わしいアプローチは、伝統的にロンボクをややハックと見なしてきました。私自身、この特徴づけにある程度同意しますが、これを悪い意味で見るのではなく、Lombokを「賢く、技術的に価値があり、独創的な代替手段」と見なします。
それでも、それをハックと見なし、この理由でLombokを使用しない開発者がいます。それは理解できますが、私の経験では、Lombokの生産性のメリットはこれらの懸念のどれよりも重要です。私は何年もの間、それを生産プロジェクトに喜んで使用しています。
詳細に入る前に、プロジェクトでロンボクを使用することを特に重視する2つの理由を要約したいと思います。
私たちが使用するJavaツールとフレームワークの多くは 豆のパターン 。 Java Beansは、デフォルトのゼロ引数コンストラクター(および場合によっては他のバージョン)を持つシリアル化可能なクラスであり、通常はプライベートフィールドに裏打ちされたゲッターとセッターを介して状態を公開します。たとえば、JPAや、JAXBやJacksonなどのシリアル化フレームワークを使用する場合などに、これらをたくさん記述します。
最大5つの属性(プロパティ)を保持するこのユーザーBeanについて考えてみます。このユーザーには、すべての属性のコンストラクター、意味のある文字列表現を追加し、メールフィールドに関して同等性/ハッシュを定義する必要があります。
public class User implements Serializable { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; // Empty constructor implementation: ~3 lines. // Utility constructor for all attributes: ~7 lines. // Getters/setters: ~38 lines. // equals() and hashCode() as per email: ~23 lines. // toString() for all attributes: ~3 lines. // Relevant: 5 lines; Boilerplate: 74 lines => 93% meaningless code :( }
ここでは簡潔にするために、すべてのメソッドの実際の実装を含めるのではなく、メソッドと実際の実装にかかったコードの行数をリストしたコメントを提供しました。その定型コードは、このクラスのコードの90%以上になります。
さらに、後で言いたい場合は、email
を変更します。 〜emailAddress
またはregistrationTs
を持っているDate
になるInstant
の代わりに次に、メソッドの名前と型の取得/設定、ユーティリティコンストラクターの変更などを変更するために、(場合によってはIDEの助けを借りて)時間を割く必要があります。繰り返しますが、私のコードに実用的なビジネス価値をもたらさない何かのための貴重な時間。
ここでロンボクがどのように役立つか見てみましょう:
import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString @EqualsAndHashCode(of = {'email'}) public class User { private String email; private String firstName; private String lastName; private Instant registrationTs; private boolean payingCustomer; }
Voilà!たくさんのlombok.*
を追加しました注釈を付けて、私が望んでいたことを実現しました。上記のリストは正確に すべて このために書く必要のあるコード。ロンボクは私のコンパイラプロセスにフックして生成されます すべて 私のために(私のIDEの以下のスクリーンショットを参照してください)。
お気づきのとおり、NetBeansインスペクター(これはIDEに関係なく発生します)は、Lombokがプロセスに追加したものを含め、コンパイルされたクラスのバイトコードを検出します。ここで起こったことは非常に簡単です。
@Getter
を使用するおよび@Setter
すべての属性のゲッターとセッターを生成するようにLombokに指示しました。これは、クラスレベルでアノテーションを使用したためです。どの属性に対して何を生成するかを選択的に指定したい場合は、フィールド自体に注釈を付けることができます。@NoArgsConstructor
そして@AllArgsConstructor
、クラスのデフォルトの空のコンストラクターと、すべての属性の追加のコンストラクターを取得しました。@ToString
注釈は便利なtoString()
を自動生成しますメソッド。デフォルトでは、名前のプレフィックスが付いたすべてのクラス属性が表示されます。equals()
のペアを作成しますおよびhashCode()
使用したメールフィールドで定義されたメソッド@EqualsAndHashCode
関連するフィールドのリスト(この場合は電子メールのみ)を使用してパラメーター化しました。これと同じ例に従って、Lombokのカスタマイズをいくつか使用してみましょう。
AccessLevel.PACKAGE
でカスタマイズしています。@NonNull
で注釈を付ける十分です。 Lombokは、NullPointerException
をスローするnullチェックを生成します。コンストラクターメソッドとセッターメソッドで適切な場合。password
を追加します属性ですが、toString()
を呼び出すときに表示されたくないセキュリティ上の理由から。これは、@ToString
のexcludes引数を介して実行されます。@Getter
そのままですが、ここでもAccessLevel.PROTECTED
を使用します@Setter
の場合。email
に何らかの制約を強制したいと思います。フィールドが変更された場合に、何らかのチェックが実行されるようにします。このために、私はsetEmail()
を実装するだけです自分でメソッド。 Lombokは、既存のメソッドの生成を省略します。Userクラスは次のようになります。
import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {'password'}) @EqualsAndHashCode(of = {'email'}) public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName; private @NonNull Instant registrationTs; private boolean payingCustomer; protected void setEmail(String email) { // Check for null (=> NullPointerException) // and valid email code (=> IllegalArgumentException) this.email = email; } }
一部のアノテーションでは、クラス属性をプレーンな文字列として指定していることに注意してください。たとえば、タイプミスや存在しないフィールドの参照を行うと、Lombokはコンパイルエラーをスローするため、問題ありません。 Lombokを使用すれば、安全です。
また、setEmail()
と同じようにメソッドの場合、Lombokは問題なく、プログラマーがすでに実装したメソッドに対しては何も生成しません。これは、すべてのメソッドとコンストラクターに適用されます。
Lombokが優れているもう1つのユースケースは、不変のデータ構造を作成する場合です。これらは通常、「値型」と呼ばれます。一部の言語にはこれらのサポートが組み込まれており、 提案 これを将来のJavaバージョンに組み込むため。
ユーザーのログインアクションへの応答をモデル化するとします。これは、インスタンス化してアプリケーションの他のレイヤーに戻す(たとえば、HTTP応答の本文としてJSONシリアル化する)オブジェクトの種類です。このようなLoginResponseは変更可能である必要はまったくなく、Lombokはこれを簡潔に説明するのに役立ちます。確かに、不変のデータ構造には他にも多くのユースケースがあります(他の品質の中でも特にマルチスレッドやキャッシュに適しています)が、この単純な例に固執しましょう。
import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.ToString; import lombok.experimental.Wither; @Getter @RequiredArgsConstructor @ToString @EqualsAndHashCode public final class LoginResponse { private final long userId; private final @NonNull String authToken; private final @NonNull Instant loginTs; @Wither private final @NonNull Instant tokenExpiryTs; }
ここで注目に値する:
@RequiredArgsConstructor
注釈が導入されました。適切な名前が付けられており、まだ初期化されていないすべての最終フィールドのコンストラクターを生成します。@Wither
の方法をご覧ください注釈はここで役立ちます:ロンボクにwithTokenExpiryTs(Instant tokenExpiryTs)
を生成するように指示します指定している新しいインスタンスを除く、すべてのwith’edインスタンス値を持つLoginResponseの新しいインスタンスを作成するメソッド。すべてのフィールドでこの動作を希望しますか? @Wither
を追加するだけです代わりにクラス宣言に。これまでに説明した両方のユースケースは非常に一般的であるため、Lombokはそれらをさらに短くするためにいくつかのアノテーションを出荷しています。@Data
でクラスにアノテーションを付けるロンボクが@Getter
で注釈されているかのように動作するようにトリガーします+ @Setter
+ @ToString
+ @EqualsAndHashCode
+ @RequiredArgsConstructor
。同様に、@Value
を使用しますクラスが不変の(そして最終的な)クラスに変わります。これも、上記のリストで注釈が付けられているかのようになります。
ユーザーの例に戻ると、新しいインスタンスを作成する場合は、最大6つの引数を持つコンストラクターを使用する必要があります。これはすでにかなり大きな数であり、クラスに属性をさらに追加するとさらに悪化します。また、lastName
にいくつかのデフォルト値を設定したいとします。およびpayingCustomer
田畑。
Lombokは非常に強力な@Builder
を実装していますこの機能により、ビルダーパターンを使用して新しいインスタンスを作成できます。それをUserクラスに追加しましょう:
import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {'password'}) @EqualsAndHashCode(of = {'email'}) @Builder public class User { private @NonNull String email; private @NonNull byte[] password; private @NonNull String firstName; private @NonNull String lastName = ''; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }
これで、次のような新しいユーザーを流暢に作成できるようになりました。
User user = User .builder() .email(' [email protected] ') .password('secret'.getBytes(StandardCharsets.UTF_8)) .firstName('Miguel') .registrationTs(Instant.now()) .build();
クラスが成長するにつれて、この構成がどれほど便利になるかを想像するのは簡単です。
「非常に正気なルールに従いたい場合は 継承よりも構成を優先する 」これは、Javaが実際には役に立たないものであり、冗長性があります。オブジェクトを作成する場合は、通常、委任メソッド呼び出しをあちこちに作成する必要があります。
Lombokは、@Delegate
を介してこれに対する解決策を提案します。例を見てみましょう。
ContactInformation
の新しい概念を導入したいとします。これは私たちのUser
に関する情報です持っているし、他のクラスにも持ってもらいたいかもしれません。次に、次のようなインターフェイスを介してこれをモデル化できます。
public interface HasContactInformation { String getEmail(); String getFirstName(); String getLastName(); }
次に、新しいContactInformation
を紹介します。 Lombokを使用したクラス:
import lombok.Data; @Data public class ContactInformation implements HasContactInformation { private String email; private String firstName; private String lastName; }
そして最後に、リファクタリングすることができますUser
ContactInformation
で作曲するLombokを使用して、インターフェイスコントラクトに一致するために必要なすべての委任呼び出しを生成します。
import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.Setter; import lombok.ToString; import lombok.experimental.Delegate; @Getter @Setter(AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PACKAGE) @AllArgsConstructor @ToString(exclude = {'password'}) @EqualsAndHashCode(of = {'contactInformation'}) public class User implements HasContactInformation { @Getter(AccessLevel.NONE) @Delegate(types = {HasContactInformation.class}) private final ContactInformation contactInformation = new ContactInformation(); private @NonNull byte[] password; private @NonNull Instant registrationTs; private boolean payingCustomer = false; }
HasContactInformation
のメソッドの実装を作成する必要がなかったことに注意してください。これは、ContactInformation
への呼び出しを委任してLombokに指示していることです。インスタンス。
また、委任されたインスタンスに外部からアクセスできるようにしたくないので、@Getter(AccessLevel.NONE)
を使用してカスタマイズし、ゲッターの生成を効果的に防止しています。
ご存知のとおり、Javaは チェックされた例外とチェックされていない例外 。これはの伝統的な情報源です 論争と批判 特に、チェックされた例外をスローするように設計されたAPIを処理する場合、開発者がそれらをキャッチするか、スローするメソッドを宣言する必要がある場合は特に、例外処理が邪魔になることがあります。
この例を考えてみましょう。
public class UserService { public URL buildUsersApiUrl() { try { return new URL('https://apiserver.com/users'); } catch (MalformedURLException ex) { // Malformed? Really? throw new RuntimeException(ex); } } }
これは非常に一般的なパターンです。URL
が原因で、URLが適切に形成されていることは確かです。コンストラクターはチェック済みの例外をスローします。強制的にキャッチするか、メソッドを宣言してスローし、同じ状況で呼び出し元を出します。これらのチェックされた例外をRuntimeException
内にラップする非常に拡張されたプラクティスです。そして、コーディングするにつれて処理する必要のあるチェック済み例外の数が増えると、これはさらに悪化します。
つまり、これはまさにLombokの@SneakyThrows
です。これは、メソッドでスローされる可能性のあるチェック済みの例外をチェックされていない例外にラップし、面倒な作業から解放するためです。
import lombok.SneakyThrows; public class UserService { @SneakyThrows public URL buildUsersApiUrl() { return new URL('https://apiserver.com/users'); } }
このように、どのくらいの頻度でロガーインスタンスをクラスに追加しますか? (( SLF4J サンプル)
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
かなり推測します。これを知って、Lombokの作成者は、カスタマイズ可能な名前(デフォルトはログ)でロガーインスタンスを作成するアノテーションを実装し、Javaプラットフォームで最も一般的なロギングフレームワークをサポートしました。ちょうどこのように(ここでも、SLF4Jベース):
import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @Slf4j public class UserService { @SneakyThrows public URL buildUsersApiUrl() { log.debug('Building users API URL'); return new URL('https://apiserver.com/users'); } }
Lombokを使用してコードを生成する場合、実際にはメソッドを記述していないため、これらのメソッドに注釈を付ける機能が失われるように見える場合があります。しかし、これは実際には真実ではありません。むしろ、Lombokを使用すると、生成されたコードに注釈を付ける方法を、やや特殊な表記法を使用して伝えることができますが、真実は伝えられます。
依存性注入フレームワークの使用を対象としたこの例を考えてみましょう。| _ + _ |があります。コンストラクタインジェクションを使用してUserService
への参照を取得するクラスおよびUserRepository
。
UserApiClient
上記のサンプルは、生成されたコンストラクターに注釈を付ける方法を示しています。 Lombokを使用すると、生成されたメソッドとパラメーターに対しても同じことができます。
この投稿で説明されているLombokの使用法は、私が長年にわたって最も有用であると個人的に見つけた機能に焦点を当てています。ただし、利用可能な他の多くの機能とカスタマイズがあります。
Lombokのドキュメント 非常に有益で徹底的です。非常に詳細な説明と例が記載された、すべての機能(注釈)専用のページがあります。この投稿がおもしろいと思ったら、lombokとそのドキュメントを深く掘り下げて詳細を確認することをお勧めします。
プロジェクトサイトには、いくつかの異なるプログラミング環境でLombokを使用する方法が記載されています。つまり、最も一般的なIDE(Eclipse、NetBeans、IntelliJ)がサポートされています。私自身、プロジェクトごとに定期的に切り替えを行い、それらすべてにLombokを完璧に使用しています。
デロンボク は「Lombokツールチェーン」の一部であり、非常に便利です。それがすることは基本的にJavaを生成することです ソースコード Lombokアノテーション付きコードの場合、Lombokで生成されたバイトコードと同じ操作を実行します。
これは、Lombokの採用を検討しているが、まだ確信が持てない人々にとっては素晴らしいオプションです。自由に使い始めることができ、「ベンダーロックイン」は発生しません。あなたまたはあなたのチームが後で選択を後悔した場合に備えて、いつでもdelombokを使用して対応するソースコードを生成でき、Lombokへの依存を維持することなく使用できます。
デザインの原則における統一
Delombokは、Lombokが何をするかを正確に知るための優れたツールでもあります。ビルドプロセスにプラグインする非常に簡単な方法があります。
Javaの世界には、アノテーションプロセッサを同様に使用して、コンパイル時にコードを強化または変更するツールが多数あります。 不変 または Google Auto Value 。これら(そして確かに他のもの!)は、機能的にLombokと重複しています。私は特にImmutablesアプローチが大好きで、いくつかのプロジェクトでも使用しています。
「バイトコード拡張」のための同様の機能を提供する他の優れたツールがあることも注目に値します。 バイトバディ または Javassist 。ただし、これらは通常、実行時に機能し、この投稿の範囲を超えた独自の世界を構成します。
同じ問題のいくつかに対処するのに役立つ、より慣用的な、または言語レベルの設計アプローチを提供する、最新のJVMターゲット言語がいくつかあります。確かに、Groovy、Scala、Kotlinは良い例です。ただし、Javaのみのプロジェクトで作業している場合、Lombokは、プログラムをより簡潔で表現力豊かで保守しやすいものにするための優れたツールです。
関連: Dart言語:JavaとC#が十分にシャープでない場合