覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Lombok - @Value

Lombokの@Value(lombok.Value)アノテーションの利用サンプルです。

クラスに@Valueアノテーションを付与することで、対象クラスをImmutableの状態にすることができます。

@Valueアノテーションを付与すると、@Getter, @ToString, @EqualsAndHashCode, @AllArgsConstructorを付与したのと同じ状態となります


@Valueアノテーションを付与したクラスの動き

以下ではクラス・メンバ共にfinalになっていることや、getterが定義されていること、継承ができないことなどを確認しています

■@Valueアノテーションを付与したクラス

import lombok.Value;

@Value
public class Person1 {

    private long id;

    private String name;
}

■利用側のコード

デフォルトコンストラクタが利用できない、setterが利用できない、継承ができないということが分かります。
また、コンストラクタの引数にnullを渡せてしまうことが分かります。

public class Person1Client {

    public static void main(String[] args) {
        long id = 5;
        String name = "Lombok Ken";

        Person1 p1 = new Person1(id, name);

        // Person1 p = new Person1();  <-- デフォルトコンストラクタは使えない

        // setterは使えない
        // p1.setId(1000);
        // p1.setName("New Name");

        System.out.println("Person1#toString: " + p1);
        System.out.println("-----------------");
        System.out.println("Person1#id: " + p1.getId());
        System.out.println("Person1#name: " + p1.getName());

        System.out.println("-----------------");

        // @Valueアノテーション自体は、nullチェックの機構までは提供していないのでコンストラクタにnullを渡せる
        Person1 p2 = new Person1(id, null);
        System.out.println("Person1#toString: " + p2);
    }

    // @Valueが付いているクラスを継承することはできない
    // static class Person1Child extends Person1 {
    //     
    // }
}

■実行結果

Person1#toString: Person1(id=5, name=Lombok Ken)
-----------------
Person1#id: 5
Person1#name: Lombok Ken
-----------------
Person1#toString: Person1(id=5, name=null)

■実際に生成されているソースコード

実際に生成されているソースコードは以下です(delombokで確認)

public final class Person1 {
    private final long id;
    private final String name;

    public Person1(final long id, final String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof Person1)) return false;
        final Person1 other = (Person1) o;
        if (this.getId() != other.getId()) return false;
        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;
        return true;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "Person1(id=" + this.getId() + ", name=" + this.getName() + ")";
    }
}


ファクトリメソッドを定義する

@Valueアノテーションに、staticConstructorパタメータを指定することで、対象クラスのインスタンスを生成するためのファクトリメソッドを定義することができます

■@Valueアノテーションを付与したクラス

ofという名前のファクトリメソッドを定義しています

import lombok.Value;

@Value(staticConstructor="of")
public class Person2 {

    private long id;

    private String name;
}

■利用側のコード

public final class Person2Client {

    public static void main(String[] args) {
        long id = 50;
        String name = "Lombok Kentaro";

        Person2 p1 = Person2.of(id, name);

        // Person2 p = new Person2(id, name);  <-- 引数付きコンストラクタは使えない

        // setterは使えない
        // p1.setId(1000);
        // p1.setName("New Name");

        System.out.println("Person2#toString: " + p1);
        System.out.println("-----------------");
        System.out.println("Person2#id: " + p1.getId());
        System.out.println("Person2#name: " + p1.getName());

        System.out.println("-----------------");

        // @Valueアノテーション自体は、nullチェックの機構までは提供していないのでコンストラクタにnullを渡せる
        Person2 p2 = Person2.of(id, null);
        System.out.println("Person2#toString: " + p2);
    }
}

■実行結果

Person2#toString: Person2(id=50, name=Lombok Kentaro)
-----------------
Person2#id: 50
Person2#name: Lombok Kentaro
-----------------
Person2#toString: Person2(id=50, name=null)

■実際に生成されているソースコード

実際に生成されているソースコードは以下です(delombokで確認)
コンストラクタがprivateになっていることが分かります。

public final class Person2 {
    private final long id;
    private final String name;

    private Person2(final long id, final String name) {
        this.id = id;
        this.name = name;
    }

    public static Person2 of(final long id, final String name) {
        return new Person2(id, name);
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof Person2)) return false;
        final Person2 other = (Person2) o;
        if (this.getId() != other.getId()) return false;
        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;
        return true;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "Person2(id=" + this.getId() + ", name=" + this.getName() + ")";
    }
}


@Valueが付与されたクラスの値は変更できないか?

@Valueを付与したクラスはImmutableになると言いましたが、メンバにCollectionなどを保持している場合は、外から値を変更できてしまいます。
防御的コピーが自動的に行われているわけではありません。

■@Valueアノテーションを付与したクラス

import java.util.Date;
import java.util.List;

import lombok.Value;

@Value
public class Person3 {

    private long id;

    private String name;

    private String[] notes;

    private List<String> friends;

    private Date birthDate;
}

■利用側のコード

コンストラクタで与えた引数の中身を書き換えると、生成したインスタンスの中のメンバの値も書き換わっていることが分かります。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class Person3Client {

    public static void main(String[] args) {
        long id = 5;
        String name = "Lombok Ken";
        String[] notes = new String[] { "note1", "note2", "note3" };
        List<String> friends = new ArrayList<>();
        friends.add("John");
        friends.add("Taro");
        friends.add("Hanako");
        Date birthDate = new Date();

        Person3 p = new Person3(id, name, notes, friends, birthDate);

        System.out.println("Person3#toString: " + p);
        System.out.println("-----------------");
        System.out.println("Person3#id: " + p.getId());
        System.out.println("Person3#name: " + p.getName());
        System.out.println("Person3#notes: " + Arrays.toString(p.getNotes()));
        System.out.println("Person3#friends: " + p.getFriends());
        System.out.println("Person3#birthDate: " + p.getBirthDate());

        System.out.println("\n################################\n");

        notes[0] = "dummy1";
        notes[1] = "dummy2";
        notes[2] = "dummy3";

        friends.add("Unknown");

        birthDate.setTime(birthDate.getTime() - 1000000000);

        System.out.println("Person3#toString: " + p);
        System.out.println("-----------------");
        System.out.println("Person3#id: " + p.getId());
        System.out.println("Person3#name: " + p.getName());
        System.out.println("Person3#notes: " + Arrays.toString(p.getNotes()));
        System.out.println("Person3#friends: " + p.getFriends());
        System.out.println("Person3#birthDate: " + p.getBirthDate());
    }
}

■実行結果

Person3#toString: Person3(id=5, name=Lombok Ken, notes=[note1, note2, note3], friends=[John, Taro, Hanako], birthDate=Tue Oct 04 12:48:25 JST 2016)
-----------------
Person3#id: 5
Person3#name: Lombok Ken
Person3#notes: [note1, note2, note3]
Person3#friends: [John, Taro, Hanako]
Person3#birthDate: Tue Oct 04 12:48:25 JST 2016

################################

Person3#toString: Person3(id=5, name=Lombok Ken, notes=[dummy1, dummy2, dummy3], friends=[John, Taro, Hanako, Unknown], birthDate=Thu Sep 22 23:01:45 JST 2016)
-----------------
Person3#id: 5
Person3#name: Lombok Ken
Person3#notes: [dummy1, dummy2, dummy3]
Person3#friends: [John, Taro, Hanako, Unknown]
Person3#birthDate: Thu Sep 22 23:01:45 JST 2016

■実際に生成されているソースコード

実際に生成されているソースコードは以下です(delombokで確認)
コンストラクタで受け取った値をそのままメンバにセットしていることが分かります。

import java.util.Date;
import java.util.List;

public final class Person3 {
    private final long id;
    private final String name;
    private final String[] notes;
    private final List<String> friends;
    private final Date birthDate;

    public Person3(final long id, final String name, final String[] notes, final List<String> friends, final Date birthDate) {
        this.id = id;
        this.name = name;
        this.notes = notes;
        this.friends = friends;
        this.birthDate = birthDate;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public String[] getNotes() {
        return this.notes;
    }

    public List<String> getFriends() {
        return this.friends;
    }

    public Date getBirthDate() {
        return this.birthDate;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof Person3)) return false;
        final Person3 other = (Person3) o;
        if (this.getId() != other.getId()) return false;
        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 (!java.util.Arrays.deepEquals(this.getNotes(), other.getNotes())) return false;
        final Object this$friends = this.getFriends();
        final Object other$friends = other.getFriends();
        if (this$friends == null ? other$friends != null : !this$friends.equals(other$friends)) return false;
        final Object this$birthDate = this.getBirthDate();
        final Object other$birthDate = other.getBirthDate();
        if (this$birthDate == null ? other$birthDate != null : !this$birthDate.equals(other$birthDate)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        result = result * PRIME + java.util.Arrays.deepHashCode(this.getNotes());
        final Object $friends = this.getFriends();
        result = result * PRIME + ($friends == null ? 43 : $friends.hashCode());
        final Object $birthDate = this.getBirthDate();
        result = result * PRIME + ($birthDate == null ? 43 : $birthDate.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "Person3(id=" + this.getId() + ", name=" + this.getName() + ", notes=" + java.util.Arrays.deepToString(this.getNotes()) + ", friends=" + this.getFriends() + ", birthDate=" + this.getBirthDate() + ")";
    }
}


@Value + Generics

Genericsが絡んだ独自クラスを定義して@Valueを付与した場合も正しく動作します。

@Valueアノテーションを付与したクラス

import java.util.List;

import lombok.Value;

@Value
public class Person4<T1, T2> {

    private long id;

    private String name;

    private List<T1> friends;

    private T2 description;
}

■利用側のコード

import java.util.ArrayList;
import java.util.List;

public class Person4Client {

    public static void main(String[] args) {
        long id = 5;
        String name = "Lombok Ken";
        List<Integer> friends = new ArrayList<>();
        friends.add(1);
        friends.add(2);
        friends.add(3);
        String description = "Sample User.";

        Person4<Integer, String> p = new Person4<>(id, name, friends, description);

        System.out.println("Person4#toString: " + p);
        System.out.println("-----------------");
        System.out.println("Person4#id: " + p.getId());
        System.out.println("Person4#name: " + p.getName());
        System.out.println("Person4#friends: " + p.getFriends());
        System.out.println("Person4#description: " + p.getDescription());
    }
}

■実行結果

Person4#toString: Person4(id=5, name=Lombok Ken, friends=[1, 2, 3], description=Sample User.)
-----------------
Person4#id: 5
Person4#name: Lombok Ken
Person4#friends: [1, 2, 3]
Person4#description: Sample User.

■実際に生成されているソースコード

実際に生成されているソースコードは以下です(delombokで確認)

import java.util.List;

public final class Person4<T1, T2> {
    private final long id;
    private final String name;
    private final List<T1> friends;
    private final T2 description;

    public Person4(final long id, final String name, final List<T1> friends, final T2 description) {
        this.id = id;
        this.name = name;
        this.friends = friends;
        this.description = description;
    }

    public long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public List<T1> getFriends() {
        return this.friends;
    }

    public T2 getDescription() {
        return this.description;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof Person4)) return false;
        final Person4<?, ?> other = (Person4<?, ?>) o;
        if (this.getId() != other.getId()) return false;
        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$friends = this.getFriends();
        final Object other$friends = other.getFriends();
        if (this$friends == null ? other$friends != null : !this$friends.equals(other$friends)) return false;
        final Object this$description = this.getDescription();
        final Object other$description = other.getDescription();
        if (this$description == null ? other$description != null : !this$description.equals(other$description)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $name = this.getName();
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        final Object $friends = this.getFriends();
        result = result * PRIME + ($friends == null ? 43 : $friends.hashCode());
        final Object $description = this.getDescription();
        result = result * PRIME + ($description == null ? 43 : $description.hashCode());
        return result;
    }

    @Override
    public String toString() {
        return "Person4(id=" + this.getId() + ", name=" + this.getName() + ", friends=" + this.getFriends() + ", description=" + this.getDescription() + ")";
    }
}



関連エントリ