1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

org.immutablesの使い方

Posted at

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

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

事前設定

mavenのpom.xmlにdependencyを設定。

<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に作成されているはず。
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クラスのメソッドで、データが取得できる。
        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が発生する)
        // listMapはImmutableオブジェクト
        listMap.list().add("item"); // java.lang.UnsupportedOperationException
        listMap.map().put("key", "value"); //java.lang.UnsupportedOperationException

from()

  • あるImmutableオブジェクトを元に別のImmutableオブジェクトを生成する場合はfromメソッドを使う。
        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アノテーションをメソッドへ付与する。
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オブジェクトを生成する。
        Item item = ImmutableItem.of("apple", 213);

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

Optional

  • Immutableクラスに任意項目を定義する場合、Optionalを使用する。
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が発生する。
        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アノテーションを付与し、デフォルトメソッドを定義する。
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オブジェクト生成時にデータを設定しないと、デフォルト値が設定される。
        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オブジェクトに設定されたデータを元に処理を行う項目を定義する。
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);
    }
}
       Receipt receipt = ImmutableReceipt.builder()
            .putItems("bread", 210)
            .putItems("milk", 100)
            .build();

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

Precondition check

  • Checkアノテーションを付与してImmutableオブジェクトが生成される際に、チェック処理を定義する。
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オブジェクト生成時にエラーを発生させることができる。
        ImmutableCart.builder().build(); // java.lang.IllegalStateException

応用編

Wrapper Types

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

  • Wrappedインタフェース

    @Value.Style(
            typeAbstract = "_*",
            typeImmutable = "*",
            visibility = ImplementationVisibility.PUBLIC,
            defaults = @Value.Immutable(builder = false, copy = false))
    public @interface Wrapped {
    }
  • Wrapperクラス
    public static abstract class Wrapper<T> {
        @Value.Parameter
        public abstract T value();

        @Override
        public String toString() {
            return getClass().getSimpleName() + "(" + value() + ")";
        }
    }
  • Valueクラスとその呼び出し方は以下の通り。
    @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);

コンパイルに失敗する場合

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?