KEIS BLOGは株式会社ケイズ・ソフトウェアが運営しています。

KEIS BLOG

Java Tips -=-= Library =-=- Lombok Part 4


前回に引き続き画期的なライブラリ【Lombok】を紹介します。

Lombokとは

読み方は「ロンボック」または、「ロンボク」と読むようです。
クラスやメソッドにアノテーションをつけるだけで、Setter、Getter、toString、equalsなど繰り返し何度も書くコードを自動保管してくれます。
簡単に説明すると、ソースコード上SetterやGetterがなくても、コンパイルエラーにはならず、クラス外からSetterやGetterが利用できます。
更に、クラスファイルには変換済みのコードをベースにコンパイルされたコードが格納されているため、実行時にはLombokが必要ありません。

使用方法

Lombokで使える機能は以下の通りです。
この中から使い勝手の良さそうな機能をチョイスして、ご紹介していこうと思います。
各機能の解説は面白かったので、翻訳内容をそのまま載せています。

– val
手間のかからないfinal変数

– @NonNull
NullPointerExceptionを気にすることをやめ、愛する方法を学んだ

– @Cleanup
自動リソース管理:close()メソッドを面倒なく安全に呼び出します

– @Getter / @Setter
`public int getFoo() {return foo;}`を二度と書くことはありません。

– @ToString
あなたのフィールドを見るためにデバッガを起動する必要はありません:ロンボクにあなたのためのtoStringを生成させてください!

– @EqualsAndHashCode
平等化が容易:オブジェクトのフィールドからhashCodeを生成し、実装と同等になります。

– @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
順序付けられたコンストラクター:引数を取らないコンストラクター、最終フィールド/非NULLフィールドごとの1つの引数、またはすべてのフィールドの1つの引数を生成します。

– @Data
すべて一緒に:@ToString、@EqualsAndHashCode、すべてのフィールドで@Getter、およびすべての非最後のフィールドで@Setter、@RequiredArgsConstructorのショートカット!

– @Value
不変クラスは非常に簡単にできました。

– @Builder
ボブのおじさん:オブジェクト作成用の面倒なファンシーパンツ!

– @SneakyThrows
前に誰も投げ入れていない場所で、チェック例外を大胆に投げる!

– @Synchronized
同期が完了しました。ロックを公開しないでください。

– @Getter(lazy=true)
怠惰は美徳です!

– @Log
キャプテンズ・ログ、スターディート 24435.7:「その行は何だったの?」

まず始めに、`@Value`アノテーションをご紹介します。

– @Value
@Valueは@Dataのfinal版で、全てのフィールドはデフォルトでprivate finalに設定されSetterは生成されません。
@Dataと同様、便利なtoString()、equals()、hashCode()メソッドも生成され、各フィールドはgetterメソッドを取得し、フィールド宣言で初期化された最終フィールドを除くすべての引数を含むコンストラクタも生成されます。

実際のコード(With Lombok)

import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
import lombok.experimental.Wither;
import lombok.ToString;

@Value public class ValueExample {
  String name;
  @Wither(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;
  
  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}

同等の機能を持つコード(Vanilla Java)

import java.util.Arrays;

public final class ValueExample {
  private final String name;
  private int age;
  private final double score;
  protected final String[] tags;
  
  @java.beans.ConstructorProperties({"name", "age", "score", "tags"})
  public ValueExample(String name, int age, double score, String[] tags) {
    this.name = name;
    this.age = age;
    this.score = score;
    this.tags = tags;
  }
  
  public String getName() {
    return this.name;
  }
  
  public int getAge() {
    return this.age;
  }
  
  public double getScore() {
    return this.score;
  }
  
  public String[] getTags() {
    return this.tags;
  }
  
  @java.lang.Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof ValueExample)) return false;
    final ValueExample other = (ValueExample)o;
    final Object this$name = this.getName();
    final Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }
  
  @java.lang.Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.getName();
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.getAge();
    final long $score = Double.doubleToLongBits(this.getScore());
    result = result * PRIME + (int)($score >>> 32 ^ $score);
    result = result * PRIME + Arrays.deepHashCode(this.getTags());
    return result;
  }
  
  @java.lang.Override
  public String toString() {
    return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }
  
  ValueExample withAge(int age) {
    return this.age == age ? this : new ValueExample(name, age, score, tags);
  }
  
  public static final class Exercise<T> {
    private final String name;
    private final T value;
    
    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }
    
    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }
    
    public String getName() {
      return this.name;
    }
    
    public T getValue() {
      return this.value;
    }
    
    @java.lang.Override
    public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof ValueExample.Exercise)) return false;
      final Exercise<?> other = (Exercise<?>)o;
      final Object this$name = this.getName();
      final Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
      final Object this$value = this.getValue();
      final Object other$value = other.getValue();
      if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
      return true;
    }
    
    @java.lang.Override
    public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      final Object $name = this.getName();
      result = result * PRIME + ($name == null ? 43 : $name.hashCode());
      final Object $value = this.getValue();
      result = result * PRIME + ($value == null ? 43 : $value.hashCode());
      return result;
    }
    
    @java.lang.Override
    public String toString() {
      return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
    }
  }
}

続いて`@Builder`アノテーションをご紹介します。

@Builderアノテーションは、クラス用の複雑なビルダーAPIを生成します。
@Builderを使用すると、クラスをコード化するために必要なコードを自動的に生成することができます。

Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();

@Builderは、クラス、コンストラクタ、またはメソッドに配置できます。

@Builderでアノテーションを付けられたメソッドは、次の7つのものを生成します。
– 静的メソッド(ビルダ)と同じ型引数を持つ内部静的クラスFooBuilder。
– ビルダーでは、ターゲットの各パラメーターに1つの’private static final’フィールドがあります。
– ビルダーでは、パッケージのprivate引数なしのコンストラクタです。
– ビルダーでは、ターゲットの各パラメータに「Setter」のようなメソッドがあります。これは、そのパラメータと同じ型と同じ名前を持ちます。上記の例のように、setter呼び出しをチェーンできるように、Builder自体を返します。
– ビルダー内:各フィールドを渡してメソッドを呼び出すbuild()メソッド。 ターゲットが返すのと同じ型を返します。
– ビルダー:分かりやすいtoString()実装。
– ターゲットを含むクラスの場合:builder()メソッド。ビルダーの新しいインスタンスを作成します。

@Singularアノテーションでパラメータの1つ(メソッドまたはコンストラクタを@Builderにアノテートする場合)またはフィールド(@Builderをクラスにアノテートを付ける場合)にアノテートを付けることにより、LombokはそのBulder nodeをコレクションとして扱い、’setter’メソッドの代わりに 2つの’Adder’メソッドが生成されます。1つの要素をコレクションに追加する要素と、別のコレクションのすべての要素をコレクションに追加する要素。コレクションを設定するだけのSetter(既に追加されたものを置き換える)は生成されません。

@Singularは、ロンボクに知られているコレクションタイプにのみ適用できます。現在サポートされているタイプは次のとおりです。

java.util:
– Iterable, Collection, and List
– Set, SortedSet, and NavigableSet
– Map, SortedMap, and NavigableMap
Guava’s com.google.common.collect:
– ImmutableCollection and ImmutableList
– ImmutableSet and ImmutableSortedSet
– ImmutableMap, ImmutableBiMap, and ImmutableSortedMap
– ImmutableTable

実際のコード(With Lombok)

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}

同等の機能を持つコード(Vanilla Java)

import java.util.Set;

public class BuilderExample {
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      // complicated switch statement to produce a compact properly sized immutable set omitted.
      // go to https://projectlombok.org/features/Singular-snippet.html to see it.
      Set<String> occupations = ...;
      return new BuilderExample(name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

@Valueと@Builderでした。
これら2つのアノテーションを使いこなせれば、高機能で可読性の高いコードが短時間で書けます。特に@Builderはおすすめです。

今回で【Lombok】についての掲載は終了です。

【関連記事】
Google App Engine 第一回
Google App Engine 第二回
Javaのライブラリを手軽にテストしたい!! Groovy入門 第1回
Google App Engine 第三回
Google App Engine 第四回
Google App Engine 第五回
Google App Engine 第六回
Google App Engine 第七回
Google App Engine 第八回
Google App Engine 第九回
Google App Engine 第十回
Google App Engine 第十一回
Google App Engine 第十二回
AngularJS入門01
AngularJS入門02
AngularJS入門03
AngularJS入門04
AngularJS入門05
AngularJS入門06
AngularJS入門07
AngularJS入門08
AngularJS入門09
攻略 Elevator Saga =基本編=
攻略 Elevator Saga =応用編=
Java Tips -=-= Library =-=- JodaTime [前半]
Java Tips -=-= Library =-=- JodaTime [後半]
Java Tips -=-= Library =-=- Lombok Part 1
Java Tips -=-= Library =-=- Lombok Part 2
Java Tips -=-= Library =-=- Lombok Part 3