Lombok 使い方メモ

# Lombok とは
- 読みは、「ロンボック」もしくは「ロンボク」。
- アノテーションを付けるだけで、 `getter`, `setter`, `toString`, `equals` などの「何度も繰り返し書くコード」をコンパイル時に自動生成してくれるようになる。
- でも、 Getter, Setter の自動生成は無闇にやるとオブジェクト指向を破壊するので、「使ってるフレームワークがどうしても Getter, Setter を要求するから仕方ないんじゃい」ってとき以外は使うべきではないと思う。

# Hello World
```groovy:build.gradle
apply plugin: 'application'

configurations {
    provided
}

sourceSets {
    main { compileClasspath += configurations.provided }
}

repositories {
    mavenCentral()
}

dependencies {
    provided 'org.projectlombok:lombok:1.16.4'
}

mainClassName = 'sample.lombok.Main'
```

```java:Main.java
package sample.lombok;

import lombok.Data;

@Data
public class Main {

    public static void main(String... args) {
        Main m = new Main();

        m.setString("Hello Lombok!!");
        m.setNumber(999);

        System.out.println(m);
    }

    private String string;
    private int number;
}
```

```bash:動作確認
$ gradle -q run
Main(string=Hello Lombok!!, number=999)
```

- `@Data` でクラスをアノテートすることで、 `getter`, `setter`, `toString` などのメソッドが自動生成されている。
- Lombok 自体はコンパイル時にのみ使用するので、依存のスコープは `provided` にする。

# IDE で使えるようにする
## Lombok のダウンロード
[こちら][1] から、 Lombok の jar ファイルをダウンロードする。

## Eclipse の場合
- ダウンロードした `lombok.jar` は実行可能 jar になっているので、ダブルクリックなどで起動する。
- Lombok のインストーラが起動するので、 `Specify location...` をクリックする。
- `eclipse.exe` が存在するフォルダを選択する。
- `Install / Update` をクリックする。

Hello World を Eclipse に取り込んでみる。

```diff:build.gradle
apply plugin: 'application'
+ apply plugin: 'eclipse'

configurations {
    provided
}

sourceSets {
    main { compileClasspath += configurations.provided }
}

repositories {
    mavenCentral()
}

dependencies {
    provided 'org.projectlombok:lombok:1.16.4'
}

mainClassName = 'sample.lombok.Main'

+ eclipse {
+     project.name = 'lombok-sample'
+ 
+     classpath {
+         plusConfigurations += [configurations.provided]
+         noExportConfigurations += [configurations.provided]
+     }
+ }
```

```bash:Eclipseプロジェクト化
$ gradle eclipse
```

既存プロジェクトとして Eclipse に取り込む。

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/b03c18f2-79f6-d1ce-a134-452eb97aa677.jpeg)

Lombok が自動生成したメソッドが認識されている。

## NetBeans の場合
※バージョンは 8.0.2

### Gradle プロジェクトの場合
特別な設定は必要ない。
NetBeans に Gradle プラグインを入れていれば、前述の Gradle プロジェクトを取り込むだけで有効になっている。

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/8d03343e-e60a-489b-697a-3f8c7fc5790c.jpeg)

### 普通のプロジェクトの場合
- プロジェクトの依存ライブラリに `lombok.jar` を追加する。
- プロジェクトのプロパティを開き、[ビルド] > [コンパイル] の「注釈処理を有効にする」と「エディタでの注釈処理を有効にする」にチェックを入れる。

# val 変数
```java:Main.java
package sample.lombok;

import java.util.Arrays;
import java.util.HashMap;

import lombok.val;


public class Main {

    public static void main(String... args) {
        val list = Arrays.asList("hoge", "fuga", "piyo");
        list.forEach(System.out::println);
        
        val map = new HashMap<String, Long>();
        map.put("hoge", 1L);
    }
}
```

- `val` という型で変数を定義すると、代入した値から良しなに型推論してくれる。

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/cd3efb0d-ed11-3e81-8514-4224dfcfe0dd.jpeg)

- 総称型もちゃんと読み取ってくれる。

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/1c0b5f73-a75f-ad66-8495-f120a5f72c5f.jpeg)

- `val` で定義した変数は `final` 修飾されているので、再代入はできない。

# @NonNull
```java:Main.java
package sample.lombok;

import lombok.NonNull;

public class Main {

    public static void main(String... args) {
        method("hoge");
        method(null);
    }
    
    private static void method(@NonNull String value) {
        System.out.println(value);
    }
}
```

```text:実行結果
hoge
Exception in thread "main" java.lang.NullPointerException: value
	at sample.lombok.Main.method(Main.java:13)
	at sample.lombok.Main.main(Main.java:10)
```

- `@NonNull` でメソッドの引数をアノテートすると、 null チェックが自動生成される。

# @Cleanup
```java:Main.java
package sample.lombok;

import lombok.Cleanup;

public class Main {

    public static void main(String... args) {
        @Cleanup Main m = new Main();
    }
    
    public void close() {
        System.out.println("close メソッドが呼ばれました");
    }
    
    public void close(String arg) {
        System.out.println("close(String) メソッドが呼ばれました");
    }
}
```

```text:実行結果
close メソッドが呼ばれました
```

- `@Cleanup` でローカル変数をアノテートすると、スコープから抜けるときに `close()` メソッドが呼ばれるようになる。

```java:実行するメソッドを指定する
package sample.lombok;

import lombok.Cleanup;

public class Main {

    public static void main(String... args) {
        @Cleanup("dispose") Main m = new Main();
    }
    
    public void close() {
        System.out.println("close メソッドが呼ばれました");
    }
    
    public void dispose() {
        System.out.println("dispose メソッドが呼ばれました");
    }
}
```

```text:実行結果
dispose メソッドが呼ばれました
```

- `value` で実行するメソッドを指定できる。

# @Getter, @Setter
## 基本
```java:Main.java
package sample.lombok;

import lombok.Getter;
import lombok.Setter;

public class Main {

    public static void main(String... args) {
        Main m = new Main();
        
        m.setValue("Hello @Getter, @Setter");
        System.out.println(m.getValue());
    }
    
    @Getter @Setter
    private String value;
}
```

```text:実行結果
Hello @Getter, @Setter
```

- `@Getter` でゲッターメソッドを、 `@Setter` でセッターメソッドを自動生成できる。

## 可視性を指定する
```java:Main.java
package sample.lombok;

import lombok.AccessLevel;
import lombok.Getter;

public class Main {
    
    @Getter(AccessLevel.PRIVATE)
    private String value;
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/6770676a-5972-9d78-c90f-ac0f6c763f7f.jpeg)

- `value` に `AccessLevel` を渡すことで可視性を指定できる。

## @Getter(lazy=true)
```java:Main.java
package sample.lombok;

import lombok.Getter;

public class Main {
    
    public static void main(String[] args) {
        Main m = new Main();
        System.out.println("Main instance is created");
        m.getLazy();
    }

    @Getter
    private final String notLazy = createValue("not lazy");
    
    @Getter(lazy=true)
    private final String lazy = createValue("lazy");
    
    private String createValue(String name) {
        System.out.println("createValue(" + name + ")");
        return null;
    }
}
```

```text:実行結果
createValue(not lazy)
Main instance is created
createValue(lazy)
```

- `@Getter` の `lazy` に true を設定すると、値の初期化をゲッターメソッドが最初に呼ばれる時まで遅延させることができる。

# @ToString
```java:Main.java
package sample.lombok;

import java.util.Arrays;
import java.util.List;

import lombok.ToString;

@ToString(exclude="ignore")
public class Main {
    
    public static void main(String[] args) {
        System.out.println(new Main());
    }
    
    private int id = 100;
    private String value = "hoge";
    private List<String> list = Arrays.asList("fizz", "buzz");
    private double ignore = 999;
}
```

```text:実行結果
Main(id=100, value=hoge, list=[fizz, buzz])
```

- `@ToString` でクラスをアノテートすると、 `toString()` メソッドが自動生成される。
- `exclude` 属性で、出力しないフィールドを指定できる。
    - クラスが相互依存してると toString() したときに無限ループが発生するので、 `exclude` で除外しておく必要がある。

# @EqualsAndHashCode
```java:Main.java
package sample.lombok;

import java.util.Arrays;
import java.util.List;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Main {
    
    public static void main(String[] args) {
        Main a = new Main();
        Main b = new Main();
        
        System.out.println("a.hash = " + a.hashCode());
        System.out.println("b.hash = " + b.hashCode());
        System.out.println(a.equals(b));
    }
    
    private int id = 100;
    private String value = "hoge";
    private List<String> list = Arrays.asList("fizz", "buzz");
}
```

```text:実行結果
a.hash = 290324031
b.hash = 290324031
true
```

- `@EqualsAndHashCode` でアノテートすると、 `equals()` メソッドと `hashCode()` メソッドが自動生成される。
- 比較は、全てのフィールドがそれぞれ一致しているかどうかで行われる。
    - DDD だと値オブジェクトに使えるかと。

# コンストラクタの自動生成
## @NoArgsConstructor
```java:Main.java
package sample.lombok;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class Main {
    
    public Main(String string) {}
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/a87a5c64-ae5a-b616-031f-5d2ad8177336.jpeg)

- `@NoArgsConstructor` でアノテートすることで、引数なしのコンストラクタを定義できる。

## @RequiredArgsConstructor
```java:Main.java
package sample.lombok;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class Main {
    
    private String optional;
    private final int required;
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/fff2e928-0927-73fd-bb1f-0fd7fff2bf99.jpeg)

- `@RequiredArgsConstructor` でアノテートすることで、 `final` で修飾されたフィールドだけを引数に受け取るコンストラクタを自動生成できる。

## @AllArgsConstructor
```java:Main.java
package sample.lombok;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Main {
    
    private String string;
    private int number;
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/ae341702-c2f1-4aec-4011-bd0673b40aee.jpeg)

- `@AllArgsConstructor` でアノテートすることで、全てのフィールドを引数に受け取るコンストラクタを自動生成できる。

## static なファクトリメソッドを定義する
```java:Main.java
package sample.lombok;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(staticName="of")
public class Main {
    
    private final String required;
    private int optional;
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/8cc610c9-e67c-0044-ef75-066c212b4b7c.jpeg)

- 各アノテーションで `staticName` 属性を指定することで、 static なファクトリメソッドを自動生成できる。

# @Data
```java:Main.java
package sample.lombok;

import lombok.Data;

@Data
public class Main {
    
    public static void main(String[] args) {
        Main a = new Main("data");
        a.setNumber(100);
        
        Main b = new Main("data");
        b.setNumber(100);
        
        System.out.println("a = " + a);
        System.out.println(a.equals(b));
    }
    
    private final String required;
    private int number;
}
```

```text:実行結果
a = Main(required=data, number=100)
true
```

- `@Data` でクラスをアノテートすると、以下のアノテーションを全て設定したのと同じ効果を得られる。
    - `@ToString`
    - `@Getter`
    - `@Setter`
    - `@RequiredArgsConstructor`
    - `@EqualsAndHashCode`

# @Value
```java:Main.java
package sample.lombok;

import lombok.Value;

@Value
public class Main {
    
    String string;
    int number;
}
```

![lombok.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/f19bb76c-c534-db8d-fa39-46e601b6dfea.jpeg)

- `@Value` でアノテートすることで、以下のアノテーションを設定したのと同じ効果を得られる。
    - `@Getter`
    - `@ToString`
    - `@EqualsAndHashCode`
    - `@AllArgsConstructor`
- さらに、クラスおよび各フィールドは `final` になる。
- さらに、各フィールドは自動で可視性が `private` になる。
- まさに DDD の値オブジェクトか。

# @Builder
```java:Main.java
package sample.lombok;

import java.util.Arrays;
import java.util.List;

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class Main {
    
    public static void main(String[] args) {
        MainBuilder builder = Main.builder()
                                  .string("test")
                                  .number(100)
                                  .list(Arrays.asList("hoge", "fuga"))
                                  .list(Arrays.asList("fizz", "buzz"));
        
        Main m = builder.build();
        
        System.out.println(m);
    }
    
    private String string;
    private int number;
    private List<String> list;
}
```

```text:実行結果
Main(string=test, number=100, list=[fizz, buzz])
```

- `@Builder` でアノテートすることで、そのクラスのビルダークラスを自動生成できる。

## @Singular
デフォルトのままだと、コレクション型のフィールドも普通に上書きセッターメソッドとして自動生成される。
追加メソッドとして自動生成したい場合は `@Singular` でフィールドをアノテートする。

```java:Main.java
package sample.lombok;

import java.util.Arrays;
import java.util.List;

import lombok.Builder;
import lombok.Singular;
import lombok.ToString;

@Builder
@ToString
public class Main {
    
    public static void main(String[] args) {
        MainBuilder builder = Main.builder()
                                  .string("test")
                                  .number(100)
                                  .list("hoge")
                                  .list("fuga")
                                  .list(Arrays.asList("fizz", "buzz"));
        
        Main m = builder.build();
        
        System.out.println(m);
    }
    
    private String string;
    private int number;
    @Singular("list")
    private List<String> list;
}
```

```text:Main.java
Main(string=test, number=100, list=[hoge, fuga, fizz, buzz])
```

# @SneakyThrows
```java:Main.java
package sample.lombok;

import lombok.SneakyThrows;

public class Main {
    
    public static void main(String[] args) {
        method();
    }
    
    @SneakyThrows
    private static void method() {
        throw new Exception("test");
    }
}
```

```text:実行結果
Exception in thread "main" java.lang.Exception: test
	at sample.lombok.Main.method(Main.java:13)
	at sample.lombok.Main.main(Main.java:8)
```

- `@SneakyThrows` でメソッドをアノテートすると、チェック例外が内部でスローされていても `throws` 句を書かなくて良くなる。
    - `sneaky` は「コソコソする」という意味。

## 無視できる例外を指定する
```java:Main.java
package sample.lombok;

import java.io.IOException;

import lombok.SneakyThrows;

public class Main {
    
    public static void main(String[] args) {
        method();
    }
    
    @SneakyThrows(IOException.class)
    private static void method() {
        try {
            throw new Exception("test");
        } catch (Exception e) {
            // catch しないとコンパイルエラー
        }
        
        throw new IOException();
    }
}
```

- `value` で例外の `Class` オブジェクトを渡すことで、指定した例外だけを無視できるようになる。

# ロガーを使えるようにする
```diff:build.gradle
dependencies {
    provided 'org.projectlombok:lombok:1.16.4'
+   compile 'org.slf4j:slf4j-simple:1.7.12'
}
```

```java:Main.java
package sample.lombok;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Main {
    
    public static void main(String[] args) {
        log.info("Hello Logger!!");
    }
}
```

```text:動作確認
[main] INFO sample.lombok.Main - Hello Logger!!
```

- `@Slf4j` でクラスをアノテートすることで、 `log` という名前の static final なロガーが使えるようなる。
- Slf4j 以外にも、以下のロガーに対応している。

| アノテーション |              ロガークラス             |
|----------------|-----------------------------------|
| `@CommonsLog`  | `org.apache.commons.logging.Log`  |
| `@Log`         | `org.apache.commons.logging.Log`  |
| `@Log4j`       | `org.apache.log4j.Logger`         |
| `@Log4j2`      | `org.apache.logging.log4j.Logger` |
| `@Slf4j`       | `org.slf4j.Logger`                |
| `@XSlf4j`      | `org.slf4j.ext.XLogger`           |




# 参考
- [Project Lombok][1]
- [Lombok - Qiita](http://qiita.com/yyoshikaw/items/32a96332cc12854ca7a3)
- [EclipseClasspath - Gradle DSL Version 2.3](http://gradle.org/docs/current/dsl/org.gradle.plugins.ide.eclipse.model.EclipseClasspath.html)
- [NetBeans instructions | Project Lombok](https://projectlombok.org/setup/netbeans.html)

[1]: https://projectlombok.org/index.html