Lombokビルダーで@Singularアノテーションを使用する

1. 概要

Lombokライブラリは、データオブジェクトを簡素化する優れた方法を提供します。 link:/intro-to-project-lombok[Project Lombok]の主要な機能の1つはlink:/lombok-builder[[email protected]_ annotation]です。不変オブジェクトを作成するためのBuilderクラスを自動的に作成します。 ただし、Lombokで生成された標準の_Builder_クラスでは、オブジェクトにコレクションを設定するのは面倒です。
このチュートリアルでは、データオブジェクト内のコレクションを操作するのに役立つ_ @ Singular_アノテーションを見ていきます。また、これから説明するように、適切なプラクティスを実施します。

2. ビルダーとコレクション

_Builder_クラスを使用すると、シンプルで流fluentな構文で不変データオブジェクトを簡単に構築できます。 Lombokの_ @ Builder_アノテーションが付けられたクラスの例を見てみましょう。
@Getter
@Builder
public class Person {
    private final String givenName;
    private final String additionalName;
    private final String familyName;
    private final List<String> tags;
}
これで、ビルダーパターンを使用して_Person_のインスタンスを作成できます。 ここで、_tags_プロパティは_List_であることに注意してください。 さらに、標準のLombok _ @ Builder_には、リスト以外のプロパティと同じように、このプロパティを設定するメソッドが用意されています。
Person person = Person.builder()
  .givenName("Aaron")
  .additionalName("A")
  .familyName("Aardvark")
  .tags(Arrays.asList("fictional","incidental"))
  .build();
これは実行可能ですが、かなり不器用な構文です。 上記で行ったように、コレクションをインラインで作成できます。 または、事前に宣言することもできます。 いずれにせよ、それはオブジェクト作成の流れを壊します。 これは_ @ Singular_アノテーションが便利な場所です。

2.1. Listsで_ @ Singular_アノテーションを使用する

_Person_オブジェクトに別の_List_を追加し、_ @ Singular_アノテーションを付けましょう。 これにより、注釈が付けられているフィールドと注釈が付けられていないフィールドを並べて表示できます。 一般的な_tags_プロパティに加えて、_interests_のリストを_Person_に追加します。
@Singular private final List<String> interests;
*値のリストを一度に1つ作成できます。*
Person person = Person.builder()
  .givenName("Aaron")
  .additionalName("A")
  .familyName("Aardvark")
  .interest("history")
  .interest("sport")
  .build();
ビルダーは、各要素を_List_に内部的に保存し、_build()_を呼び出すときに適切な_Collection_を作成します。

2.2. 他の_Collection_タイプの使用

ここでは_ @ Singular_が_java.util.List_で機能することを示しましたが、*他のJava _Collection_クラスにも適用できます*。 _Person_にさらにメンバーを追加しましょう。
@Singular private final Set<String> skills;
@Singular private final Map<String, LocalDate> awards;
__Builder__sに関する限り、_Set_は_List_と同じように動作します。要素を1つずつ追加できます。
Person person = Person.builder()
  .givenName("Aaron")
  .skill("singing")
  .skill("dancing")
  .build();
_Set_は重複をサポートしていないため、同じ要素を複数回追加しても複数の要素は作成されないことに注意する必要があります。 _Builder_はこの状況を寛容に処理します。 要素を複数回追加できますが、作成された_Set_には要素が1つしかありません。
__Map__sは、適切なタイプのキーと値を取るメソッドを公開する_Builder_を使用して、わずかに異なる方法で処理されます。
Person person = Person.builder()
  .givenName("Aaron")
  .award("Singer of the Year", LocalDate.now().minusYears(5))
  .award("Best Dancer", LocalDate.now().minusYears(2))
  .build();
__Set__sで見たように、ビルダーは_Map_キーの重複に対して寛容であり、同じキーが複数回割り当てられた場合、最後の値を使用します。

3. _ @ Singular_メソッドの命名

これまでのところ、注意を引くことなく_ @ Singular_アノテーションの魔法に少し依存しています。 _Builder_自体は、複数形を使用するコレクション全体を一度に割り当てる方法(たとえば、「_ awards_」)を提供します。 * _ @ Singular_アノテーションによって追加される追加のメソッドは、単数形を使用します*-たとえば、「_ award_」。
Lombokは、英語の単純な複数形の単語を規則的なパターンで認識できるほどスマートです。 これまでに使用したすべての例で、最後の「s」が削除されるだけです。
また、「es」で終わる単語の場合、最後の2文字を削除することもわかります。 たとえば、「草」は「草」の単数形であり、「ぶどう」ではなく「ぶどう」は「ぶどう」の単数形であることを知っています。 ただし、場合によっては、何らかの支援が必要になります。
魚と海草を含む海の簡単なモデルを作成しましょう。
@Getter
@Builder
public class Sea {
    @Singular private final List<String> grasses;
    @Singular private final List<String> fish;
}
ロンボクは「草」という単語を処理できますが、「魚」では失われます。 英語では、単数形と複数形は同じですが、奇妙なことに十分です。 このコードはコンパイルされず、エラーが発生します。
Can't singularize this name; please specify the singular explicitly (i.e. @Singular("sheep"))
単一のメソッド名として使用する値を注釈に追加することにより、物事を整理できます。
@Singular("oneFish") private final List<String> fish;
コードをコンパイルして、_Builder_を使用できます。
Sea sea = Sea.builder()
  .grass("Dulse")
  .grass("Kelp")
  .oneFish("Cod")
  .oneFish("Mackerel")
  .build();
この場合、かなり工夫された_oneFish()_を選択しましたが、明確な複数形を持つ非標準の単語で同じメソッドを使用できます。 たとえば、_children_の_List_には、メソッド_child()_を指定できます。

4. 不変性

_ @ Singular_アノテーションがLombokでのコレクションの操作にどのように役立つかを見てきました。 利便性と表現力を提供するだけでなく、コードをクリーンに保つのにも役立ちます。
不変オブジェクトは、作成後は変更できないオブジェクトとして定義されます。 不変は、たとえば、副作用のないことを保証してオブジェクトをメソッドに渡すことができるため、リアクティブアーキテクチャでは重要です。 *ビルダーパターンは、不変性をサポートするために、POJOゲッターおよびセッターの代替として最も一般的に使用されます。*
データオブジェクトに_Collection_クラスが含まれている場合、不変性を少しずらすのは簡単です。 基本コレクションインターフェイス(_List _、_ Set_、および_Map_)にはすべて、可変および不変の実装があります。 標準のLombokビルダーに依存している場合、誤って変更可能なコレクションを渡してから変更できます。
List<String> tags= new ArrayList();
tags.add("fictional");
tags.add("incidental");
Person person = Person.builder()
  .givenName("Aaron")
  .tags(tags)
  .build();
person.getTags().clear();
person.getTags().add("non-fictional");
person.getTags().add("important");
この単純な例では、間違いを犯すためにかなりの努力をしなければなりませんでした。 たとえば、_Arrays.asList()_を使用して変数_tags_を作成した場合、無料で不変リストを取得し、_add()_または_clear()_を呼び出すと_UnsupportedOperationException_がスローされます。
実際のコーディングでは、たとえばコレクションがパラメーターとして渡される場合、エラーが発生する可能性が高くなります。 ただし、* @ _ Singular_を使用すると、ベースの_Collection_インターフェイスを操作し、_build()_ *を呼び出すときに不変のインスタンスを取得できることを知っておくと便利です。

5. 結論

このチュートリアルでは、Lombok _ @ Singular_アノテーションが、Builderパターンを使用して_List _、_ Set_、および_Map_インターフェイスを操作する便利な方法を提供する方法を見てきました。 Builderパターンは不変性をサポートし、_ @ Singular_はこのための最高のサポートを提供します。
いつものように、完全なコード例はhttps://github.com/eugenp/tutorials/tree/master/lombok[GitHubで]から入手できます。