org.immutablesの使い方 

Javaのorg.Immutablesの使い方について整理する。

公式サイトは以下。（基本的に本記事の内容は、これを参考にしている）
https://immutables.github.io/

# 事前設定
mavenのpom.xmlにdependencyを設定。

```xml
<dependency>
  <groupId>org.immutables</groupId>
  <artifactId>value</artifactId>
  <version>2.8.2</version>
  <scope>provided</scope>
</dependency>
```

# 基本的な使い方

データを格納するValueクラスを作成し、それを元に自動生成されたImmutableクラスをbuildすることで、Immutableオブジェクトを生成する。
変更を加えられたくないオブジェクトを生成する場合などに使用。


## Value
- Immutableアノテーションで宣言する。
- interfaceでもclassでもOK。
- コンパイルされるとImmutableクラスが自動生成される。
- Immutableクラスのクラス名はImmutableXxx。（XxxはValueクラスのクラス名）
- 自動生成されたImmutableクラスは、maven compileでコンパイルした場合、デフォルトならtarget/generated-sources/annotationsに作成されているはず。


```java
import java.util.List;

import org.immutables.value.Value.Immutable;

@Immutable
public interface User {
    String name();
    String address();
    int age();
    List<String> hobbies();
}
```

## Builder
- Valueクラス（上記例ではUserクラス）から自動生成されたImmutableクラスを使用する。
- Immutableクラスのbuilderメソッドを呼び出し、データを代入、最後にbuildメソッドを呼び出す。
- これでImmutableなオブジェクトが生成される。
- Valueクラスのメソッドで、データが取得できる。

```java
        User user = ImmutableUser.builder()
            .name("Tom")
            .age(21)
            .address("Tokyo, Japan")
            .addHobbies("reading", "skiing")
            .build();

        System.out.println(user.toString());
        // User{name=Tom, address=Tokyo, Japan, age=21, hobbies=[reading, skiing]}

        System.out.println(user.name());
        // Tom

        System.out.println(user.age());
        // 21
```

- Immutableオブジェクトにデータを追加することはできない。(ListクラスやMapクラスにデータを追加しようとすると、UnsupportedOperationExceptionが発生する)

```java
        // listMapはImmutableオブジェクト
        listMap.list().add("item"); // java.lang.UnsupportedOperationException
        listMap.map().put("key", "value"); //java.lang.UnsupportedOperationException

```

## from()
- あるImmutableオブジェクトを元に別のImmutableオブジェクトを生成する場合はfromメソッドを使う。


```java
        User userTemp = ImmutableUser.builder()
            .name("Tom")
            .age(21)
            .address("Tokyo, Japan")
            .addHobbies("reading", "skiing")
            .build();

        User user = ImmutableUser.builder()
            .from(userTemp)
            .address("Osaka, Japan")
            .build();

        System.out.println(user.toString());
        // User{name=Tom, address=Osaka, Japan, age=21, hobbies=[reading, skiing]}
```

## Constructor
- コンストラクターでImmutableオブジェクトを生成する場合は、Parameterアノテーションをメソッドへ付与する。

```java
import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Parameter;

@Immutable
public interface Item {
    @Parameter
    String name();
    @Parameter
    int price();
}
```

- Immutableクラスのofメソッドにデータを与えて、Immutableオブジェクトを生成する。

```java
        Item item = ImmutableItem.of("apple", 213);

        System.out.println(item);
        // Item{name=apple, price=213}

```

## Optional

- Immutableクラスに任意項目を定義する場合、Optionalを使用する。

```java
import java.util.Optional;

import org.immutables.value.Value.Immutable;

@Immutable
public interface Address {
    String country();
    String city();
    String blockNmuber();
    Optional<String> apartmentName();
}
```

- Optionalで定義された項目はデータを設定しなくてもよいが、そうでない項目はデータを設定しないとIllegalStateExceptionが発生する。

```java
        Address address1=ImmutableAddress.builder()
            .country("Japan")
            .city("Yokohama")
            .blockNmuber("1-2-3")
            .build(); // OK

        Address address2=ImmutableAddress.builder()
            .country("Japan")
            .city("Chiba")
            .blockNmuber("1-2-3")
            .apartmentName("YYY Apartment")
            .build(); // OK
        
        Address address3=ImmutableAddress.builder()
            .country("Japan")
            .city("Chiba")
            .build(); // java.lang.IllegalStateException: Cannot build Address, some of required attributes are not set [blockNmuber]

```

## Default

- デフォルト値を持たせたい項目にDefaultアノテーションを付与し、デフォルトメソッドを定義する。

```java
import org.immutables.value.Value.Default;
import org.immutables.value.Value.Immutable;

@Immutable
public interface AddressWithDefault {
    @Default
    default String country(){
        return "Japan";
    }
    String city();
    String blockNmuber();
}
```

- Immutableオブジェクト生成時にデータを設定しないと、デフォルト値が設定される。

```java
        AddressWithDefault address = ImmutableAddressWithDefault.builder()
            .city("Saitama")
            .blockNmuber("4-5-6")
            .build();

        System.out.println(address.toString());
        // AddressWithDefault{country=Japan, city=Saitama, blockNmuber=4-5-6}

```

## Derived

- Derivedアノテーションを付与して、Immutableオブジェクトに設定されたデータを元に処理を行う項目を定義する。

```java
import java.util.Map;

import org.immutables.value.Value.Derived;
import org.immutables.value.Value.Immutable;

@Immutable
public abstract class Receipt {
    abstract Map<String, Integer> items();

    @Derived
    public int total() {
        // itemsのvalueを合計する
        return items().values().stream().reduce(0, Integer::sum);
    }
}
```

```java
       Receipt receipt = ImmutableReceipt.builder()
            .putItems("bread", 210)
            .putItems("milk", 100)
            .build();

        System.out.println(receipt.total());
        // 310
```

## Precondition check

- Checkアノテーションを付与してImmutableオブジェクトが生成される際に、チェック処理を定義する。

``` java
import java.util.List;

import org.immutables.value.Value.Check;
import org.immutables.value.Value.Immutable;

@Immutable
public abstract class Cart {
    abstract List<String> items();

    @Check
    protected void check() {
        if (items() == null || items().isEmpty()) {
            throw new IllegalStateException();
        }
    }
}
```

- チェック条件を満たさないとImmutableオブジェクト生成時にエラーを発生させることができる。

```java
        ImmutableCart.builder().build(); // java.lang.IllegalStateException
```

# 応用編

## Wrapper Types

- WrappedインタフェースとWrapperクラスを用意しておけば、Valueクラスの定義がシンプルになり、同じようなコードを何度も書かなくてよくなる。

- Wrappedインタフェース

```java
    @Value.Style(
            typeAbstract = "_*",
            typeImmutable = "*",
            visibility = ImplementationVisibility.PUBLIC,
            defaults = @Value.Immutable(builder = false, copy = false))
    public @interface Wrapped {
    }
```

- Wrapperクラス

```java
    public static abstract class Wrapper<T> {
        @Value.Parameter
        public abstract T value();

        @Override
        public String toString() {
            return getClass().getSimpleName() + "(" + value() + ")";
        }
    }
```

- Valueクラスとその呼び出し方は以下の通り。

```java
    @Value.Immutable
    @Wrapped
    public static abstract class _FirstName extends Wrapper<String> {
    }

    @Value.Immutable
    @Wrapped
    public static abstract class _LastName extends Wrapper<String> {
    }

    @Value.Immutable
    @Wrapped
    public static abstract class _Age extends Wrapper<Integer> {
    }

    FirstName firstName = FirstName.of("Taro");

    LastName lastName = LastName.of("Yamada");

    Age age = Age.of(45);
```

## コンパイルに失敗する場合
- `mvn clean compile`のように、クリーンしてからコンパイルする。
- useIncrementalCompilationオプションにfalseを設定する。
    - https://immutables.github.io/getstarted.html#troubleshooting