読者です 読者をやめる 読者になる 読者になる

覚えたら書く

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

Lombok - @Builder

Java Lombok

Lombokの@Builder(lombok.Builder)アノテーションの利用サンプルです。
対象クラスを生成するためのBuilderクラスを自動的に生成してくれます。@Valueアノテーションと組み合わせると強力です


@Builderを付与してみる

@Builderをクラスに付与して自動的にBuilderクラスが生成されることを確認してみます。

■サンプルコード

import java.util.List;

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class Person1 {

    private final long id;

    private final String name;

    private final List<String> friends;
}

■呼び出し側のコード

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

public final class Person1Client {

    public static void main(String[] args) {
        List<String> friends = Arrays.asList("ken", "hanako", "ogura");

        Person1 p1 = Person1.builder()
                .id(10L)
                .name("Taro")
                .friends(friends)
                .build();

        System.out.println("Person1#toString: " + p1);
    }
}

■実行結果

Person1#toString: Person1(id=10, name=Taro, friends=[ken, hanako, ogura])

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

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

import java.util.List;

public class Person1 {
    private final long id;
    private final String name;
    private final List<String> friends;

    Person1(final long id, final String name, final List<String> friends) {
        this.id = id;
        this.name = name;
        this.friends = friends;
    }


    public static class Person1Builder {
        private long id;
        private String name;
        private List<String> friends;

        Person1Builder() {
        }

        public Person1Builder id(final long id) {
            this.id = id;
            return this;
        }

        public Person1Builder name(final String name) {
            this.name = name;
            return this;
        }

        public Person1Builder friends(final List<String> friends) {
            this.friends = friends;
            return this;
        }

        public Person1 build() {
            return new Person1(id, name, friends);
        }

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

    public static Person1Builder builder() {
        return new Person1Builder();
    }

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


Builderクラスに関する名称を指定する

@Builderアノテーションを付与すると自動的にBuilderクラスや関連するメソッドが生成されますが、その名前はデフォルトで以下のようになっています。

  • Builderクラスのクラス名・・・生成対象のクラス名 + Builder
  • Builderクラスのインスタンスを生成するためのメソッド名・・・builder
  • 生成対象のクラスを生成するメソッド名・・・build

これらを変更したい場合は、builderClassName, builderMethodName, buildMethodName のパラメータに名前を指定します

■サンプルコード

import java.util.List;

import lombok.Builder;

public class Person2 {

    @Builder
    public static class Person2a {

        private final long id;

        private final String name;

        private final List<String> friends;
    }

    @Builder(builderClassName="Builder", builderMethodName="newBuilder", buildMethodName="create")
    public static class Person2b {

        private final long id;

        private final String name;

        private final List<String> friends;
    }
}

■呼び出し側のコード

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

import trial.lombok.builder.Person2.Person2a;
import trial.lombok.builder.Person2.Person2b;

public final class Person2Client {

    public static void main(String[] args) {
        List<String> friends = Arrays.asList("ken", "hanako", "ogura");

        Person2a.Person2aBuilder builder1 = Person2a.builder();

        Person2a p1 = builder1.id(10L)
            .name("Taro")
            .friends(friends)
            .build();


        Person2b.Builder builder2 = Person2b.newBuilder();

        Person2b p2 = builder2.id(10L)
                .name("Taro")
                .friends(friends)
                .create();
    }
}

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

実際に生成されているソースコードは以下です(delombokで確認)
Person2bクラスのBuilderクラスに関する名称が指定したものになっていることが分かります。

import java.util.List;

public class Person2 {

    public static class Person2a {
        private final long id;
        private final String name;
        private final List<String> friends;

        Person2a(final long id, final String name, final List<String> friends) {
            this.id = id;
            this.name = name;
            this.friends = friends;
        }


        public static class Person2aBuilder {
            private long id;
            private String name;
            private List<String> friends;

            Person2aBuilder() {
            }

            public Person2aBuilder id(final long id) {
                this.id = id;
                return this;
            }

            public Person2aBuilder name(final String name) {
                this.name = name;
                return this;
            }

            public Person2aBuilder friends(final List<String> friends) {
                this.friends = friends;
                return this;
            }

            public Person2a build() {
                return new Person2a(id, name, friends);
            }

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

        public static Person2aBuilder builder() {
            return new Person2aBuilder();
        }
    }


    public static class Person2b {
        private final long id;
        private final String name;
        private final List<String> friends;

        Person2b(final long id, final String name, final List<String> friends) {
            this.id = id;
            this.name = name;
            this.friends = friends;
        }


        public static class Builder {
            private long id;
            private String name;
            private List<String> friends;

            Builder() {
            }

            public Builder id(final long id) {
                this.id = id;
                return this;
            }

            public Builder name(final String name) {
                this.name = name;
                return this;
            }

            public Builder friends(final List<String> friends) {
                this.friends = friends;
                return this;
            }

            public Person2b create() {
                return new Person2b(id, name, friends);
            }

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

        public static Builder newBuilder() {
            return new Builder();
        }
    }
}


@Singularアノテーションの指定

Collectionのメンバが存在する場合に、Builderクラスでは単純にそのコレクションの参照を渡すだけになっています。
@SingularアノテーションをCollectionのメンバに指定すると、Builderクラスにもう少し賢いメソッドが生成されます
(※ちなみに@Singularアノテーションは配列のメンバには指定できません)

■サンプルコード

Collectionのメンバに@Singularを付与しています。
メンバ名が"s"で終わっていない場合は、@Singularのパラメータに1要素を意味する名称を指定する必要があります。

import java.util.List;

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

@Builder
@ToString
public class Person3a {

    private final long id;

    private final String name;

    @Singular
    private final List<String> friends;
}
import java.util.Set;

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

@Builder
@ToString
public class Person3b {

    private final long id;

    private final String name;

    @Singular
    private final Set<String> friends;
}
import java.util.Map;

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

@Builder
@ToString
public class Person3c {

    private final long id;

    private final String name;

    @Singular("friend")
    private final Map<Integer, String> friendMap;
}

■呼び出し側のコード

BuilderクラスでCollection全体ではなく、1要素を受け取るためのメソッド(本例では freindメソッド)が利用できていることが分かります

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class Person3Client {

    public static void main(String[] args) {
        List<String> friends = Arrays.asList("ken", "hanako", "ogura");

        Person3a p1 = Person3a.builder()
                .id(20L)
                .name("Taro")
                .friend("takako")    // <-- 1要素を追加するメソッド
                .friends(friends)
                .build();

        System.out.println("Person3a#toString: " + p1);

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

        Person3b p2 = Person3b.builder()
                .id(30L)
                .name("Taro")
                .friend("Yataro")
                .friend("Goto")    // <-- 1要素を追加するメソッド
                .friends(friends)
                .build();

        System.out.println("Person3b#toString: " + p2);

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

        Map<Integer, String> friendMap = new HashMap<>();
        friendMap.put(1, "Ken");
        friendMap.put(2, "Hanako");
        friendMap.put(3, "Ogura");

        Person3c p3 = Person3c.builder()
                .id(40L)
                .name("Taro")
                .friend(10, "Takako")    // <-- 1要素を追加するメソッド
                .friendMap(friendMap)
                .build();

        System.out.println("Person3c#toString: " + p3);
    }
}

■実行結果

Person3a#toString: Person3a(id=20, name=Taro, friends=[takako, ken, hanako, ogura])
-----------------
Person3b#toString: Person3b(id=30, name=Taro, friends=[Yataro, Goto, ken, hanako, ogura])
-----------------
Person3c#toString: Person3c(id=40, name=Taro, friendMap={10=Takako, 1=Ken, 2=Hanako, 3=Ogura})

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

実際に生成されているソースコードは以下です(delombokで確認)
Builderクラスに1要素追加するためのメソッドと要素のコピーを受け取るためのメソッドが生成されていることが分かります。
またBuilderのbuildメソッドでは、コレクションの要素数に応じて適切な形でメンバにセットされていることもわかります

import java.util.List;

public class Person3a {
    private final long id;
    private final String name;
    private final List<String> friends;

    Person3a(final long id, final String name, final List<String> friends) {
        this.id = id;
        this.name = name;
        this.friends = friends;
    }


    public static class Person3aBuilder {
        private long id;
        private String name;
        private java.util.ArrayList<String> friends;

        Person3aBuilder() {
        }

        public Person3aBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person3aBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person3aBuilder friend(final String friend) {
            if (this.friends == null) this.friends = new java.util.ArrayList<String>();
            this.friends.add(friend);
            return this;
        }

        public Person3aBuilder friends(final java.util.Collection<? extends String> friends) {
            if (this.friends == null) this.friends = new java.util.ArrayList<String>();
            this.friends.addAll(friends);
            return this;
        }

        public Person3aBuilder clearFriends() {
            if (this.friends != null) this.friends.clear();
            return this;
        }

        public Person3a build() {
            java.util.List<String> friends;
            switch (this.friends == null ? 0 : this.friends.size()) {
            case 0:
                friends = java.util.Collections.emptyList();
                break;

            case 1:
                friends = java.util.Collections.singletonList(this.friends.get(0));
                break;

            default:
                friends = java.util.Collections.unmodifiableList(new java.util.ArrayList<String>(this.friends));
            }
            return new Person3a(id, name, friends);
        }

        @Override
        public String toString() {
            return "Person3a.Person3aBuilder(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
        }
    }

    public static Person3aBuilder builder() {
        return new Person3aBuilder();
    }

    @Override
    public String toString() {
        return "Person3a(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
    }
}
import java.util.Set;

public class Person3b {
    private final long id;
    private final String name;
    private final Set<String> friends;

    Person3b(final long id, final String name, final Set<String> friends) {
        this.id = id;
        this.name = name;
        this.friends = friends;
    }


    public static class Person3bBuilder {
        private long id;
        private String name;
        private java.util.ArrayList<String> friends;

        Person3bBuilder() {
        }

        public Person3bBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person3bBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person3bBuilder friend(final String friend) {
            if (this.friends == null) this.friends = new java.util.ArrayList<String>();
            this.friends.add(friend);
            return this;
        }

        public Person3bBuilder friends(final java.util.Collection<? extends String> friends) {
            if (this.friends == null) this.friends = new java.util.ArrayList<String>();
            this.friends.addAll(friends);
            return this;
        }

        public Person3bBuilder clearFriends() {
            if (this.friends != null) this.friends.clear();
            return this;
        }

        public Person3b build() {
            java.util.Set<String> friends;
            switch (this.friends == null ? 0 : this.friends.size()) {
            case 0:
                friends = java.util.Collections.emptySet();
                break;

            case 1:
                friends = java.util.Collections.singleton(this.friends.get(0));
                break;

            default:
                friends = new java.util.LinkedHashSet<String>(this.friends.size() < 1073741824 ? 1 + this.friends.size() + (this.friends.size() - 3) / 3 : Integer.MAX_VALUE);
                friends.addAll(this.friends);
                friends = java.util.Collections.unmodifiableSet(friends);
            }
            return new Person3b(id, name, friends);
        }

        @Override
        public String toString() {
            return "Person3b.Person3bBuilder(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
        }
    }

    public static Person3bBuilder builder() {
        return new Person3bBuilder();
    }

    @Override
    public String toString() {
        return "Person3b(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
    }
}
import java.util.Map;

public class Person3c {
    private final long id;
    private final String name;
    private final Map<Integer, String> friendMap;

    Person3c(final long id, final String name, final Map<Integer, String> friendMap) {
        this.id = id;
        this.name = name;
        this.friendMap = friendMap;
    }


    public static class Person3cBuilder {
        private long id;
        private String name;
        private java.util.ArrayList<Integer> friendMap$key;
        private java.util.ArrayList<String> friendMap$value;

        Person3cBuilder() {
        }

        public Person3cBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person3cBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person3cBuilder friend(final Integer friendKey, final String friendValue) {
            if (this.friendMap$key == null) {
                this.friendMap$key = new java.util.ArrayList<Integer>();
                this.friendMap$value = new java.util.ArrayList<String>();
            }
            this.friendMap$key.add(friendKey);
            this.friendMap$value.add(friendValue);
            return this;
        }

        public Person3cBuilder friendMap(final java.util.Map<? extends Integer, ? extends String> friendMap) {
            if (this.friendMap$key == null) {
                this.friendMap$key = new java.util.ArrayList<Integer>();
                this.friendMap$value = new java.util.ArrayList<String>();
            }
            for (final java.util.Map.Entry<? extends Integer, ? extends String> $lombokEntry : friendMap.entrySet()) {
                this.friendMap$key.add($lombokEntry.getKey());
                this.friendMap$value.add($lombokEntry.getValue());
            }
            return this;
        }

        public Person3cBuilder clearFriendMap() {
            if (this.friendMap$key != null) {
                this.friendMap$key.clear();
                this.friendMap$value.clear();
            }
            return this;
        }

        public Person3c build() {
            java.util.Map<Integer, String> friendMap;
            switch (this.friendMap$key == null ? 0 : this.friendMap$key.size()) {
            case 0:
                friendMap = java.util.Collections.emptyMap();
                break;

            case 1:
                friendMap = java.util.Collections.singletonMap(this.friendMap$key.get(0), this.friendMap$value.get(0));
                break;

            default:
                friendMap = new java.util.LinkedHashMap<Integer, String>(this.friendMap$key.size() < 1073741824 ? 1 + this.friendMap$key.size() + (this.friendMap$key.size() - 3) / 3 : Integer.MAX_VALUE);
                for (int $i = 0; $i < this.friendMap$key.size(); $i++) friendMap.put(this.friendMap$key.get($i), this.friendMap$value.get($i));
                friendMap = java.util.Collections.unmodifiableMap(friendMap);
            }
            return new Person3c(id, name, friendMap);
        }

        @Override
        public String toString() {
            return "Person3c.Person3cBuilder(id=" + this.id + ", name=" + this.name + ", friendMap$key=" + this.friendMap$key + ", friendMap$value=" + this.friendMap$value + ")";
        }
    }

    public static Person3cBuilder builder() {
        return new Person3cBuilder();
    }

    @Override
    public String toString() {
        return "Person3c(id=" + this.id + ", name=" + this.name + ", friendMap=" + this.friendMap + ")";
    }
}


Guavaと@Singularアノテーション

@Singularアノテーションは、Java標準のコレクションだけでなく、GuavaImmutableList, ImmutableSet, ImmutableMapにも対応しています

■サンプルコード

Guavaのコレクションのメンバに@Singularアノテーションを付与しています

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

import com.google.common.collect.ImmutableList;

@Builder
@ToString
public class Person4a {

    private final long id;

    private final String name;

    @Singular
    private final ImmutableList<String> friends;
}
import lombok.Builder;
import lombok.Singular;
import lombok.ToString;

import com.google.common.collect.ImmutableSet;

@Builder
@ToString
public class Person4b {

    private final long id;

    private final String name;

    @Singular
    private final ImmutableSet<String> friends;
}
import lombok.Builder;
import lombok.Singular;
import lombok.ToString;

import com.google.common.collect.ImmutableMap;

@Builder
@ToString
public class Person4c {

    private final long id;

    private final String name;

    @Singular("friend")
    private final ImmutableMap<Integer, String> friendMap;
}

■呼び出し側のコード

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class Person4Client {

    public static void main(String[] args) {
        List<String> friends = Arrays.asList("ken", "hanako", "ogura");

        Person4a p1 = Person4a.builder()
                .id(20L)
                .name("Taro")
                .friend("takako")
                .friends(friends)
                .build();

        System.out.println("Person4a#toString: " + p1);

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

        Person4b p2 = Person4b.builder()
                .id(30L)
                .name("Taro")
                .friend("Yataro")
                .friend("Goto")
                .friends(friends)
                .build();

        System.out.println("Person4b#toString: " + p2);

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

        Map<Integer, String> friendMap = new HashMap<>();
        friendMap.put(1, "Ken");
        friendMap.put(2, "Hanako");
        friendMap.put(3, "Ogura");

        Person4c p3 = Person4c.builder()
                .id(40L)
                .name("Taro")
                .friend(10, "Takako")
                .friendMap(friendMap)
                .build();

        System.out.println("Person4c#toString: " + p3);
    }
}

■実行結果

Person4a#toString: Person4a(id=20, name=Taro, friends=[takako, ken, hanako, ogura])
-----------------
Person4b#toString: Person4b(id=30, name=Taro, friends=[Yataro, Goto, ken, hanako, ogura])
-----------------
Person4c#toString: Person4c(id=40, name=Taro, friendMap={10=Takako, 1=Ken, 2=Hanako, 3=Ogura})

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

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

import com.google.common.collect.ImmutableList;

public class Person4a {
    private final long id;
    private final String name;
    private final ImmutableList<String> friends;

    Person4a(final long id, final String name, final ImmutableList<String> friends) {
        this.id = id;
        this.name = name;
        this.friends = friends;
    }


    public static class Person4aBuilder {
        private long id;
        private String name;
        private com.google.common.collect.ImmutableList.Builder<String> friends;

        Person4aBuilder() {
        }

        public Person4aBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person4aBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person4aBuilder friend(final String friend) {
            if (this.friends == null) this.friends = com.google.common.collect.ImmutableList.builder();
            this.friends.add(friend);
            return this;
        }

        public Person4aBuilder friends(final java.lang.Iterable<? extends String> friends) {
            if (this.friends == null) this.friends = com.google.common.collect.ImmutableList.builder();
            this.friends.addAll(friends);
            return this;
        }

        public Person4aBuilder clearFriends() {
            this.friends = null;
            return this;
        }

        public Person4a build() {
            com.google.common.collect.ImmutableList<String> friends = this.friends == null ? com.google.common.collect.ImmutableList.<String>of() : this.friends.build();
            return new Person4a(id, name, friends);
        }

        @Override
        public String toString() {
            return "Person4a.Person4aBuilder(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
        }
    }

    public static Person4aBuilder builder() {
        return new Person4aBuilder();
    }

    @Override
    public String toString() {
        return "Person4a(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
    }
}
import com.google.common.collect.ImmutableSet;

public class Person4b {
    private final long id;
    private final String name;
    private final ImmutableSet<String> friends;

    Person4b(final long id, final String name, final ImmutableSet<String> friends) {
        this.id = id;
        this.name = name;
        this.friends = friends;
    }


    public static class Person4bBuilder {
        private long id;
        private String name;
        private com.google.common.collect.ImmutableSet.Builder<String> friends;

        Person4bBuilder() {
        }

        public Person4bBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person4bBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person4bBuilder friend(final String friend) {
            if (this.friends == null) this.friends = com.google.common.collect.ImmutableSet.builder();
            this.friends.add(friend);
            return this;
        }

        public Person4bBuilder friends(final java.lang.Iterable<? extends String> friends) {
            if (this.friends == null) this.friends = com.google.common.collect.ImmutableSet.builder();
            this.friends.addAll(friends);
            return this;
        }

        public Person4bBuilder clearFriends() {
            this.friends = null;
            return this;
        }

        public Person4b build() {
            com.google.common.collect.ImmutableSet<String> friends = this.friends == null ? com.google.common.collect.ImmutableSet.<String>of() : this.friends.build();
            return new Person4b(id, name, friends);
        }

        @Override
        public String toString() {
            return "Person4b.Person4bBuilder(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
        }
    }

    public static Person4bBuilder builder() {
        return new Person4bBuilder();
    }

    @Override
    public String toString() {
        return "Person4b(id=" + this.id + ", name=" + this.name + ", friends=" + this.friends + ")";
    }
}
import com.google.common.collect.ImmutableMap;

public class Person4c {
    private final long id;
    private final String name;
    private final ImmutableMap<Integer, String> friendMap;

    Person4c(final long id, final String name, final ImmutableMap<Integer, String> friendMap) {
        this.id = id;
        this.name = name;
        this.friendMap = friendMap;
    }


    public static class Person4cBuilder {
        private long id;
        private String name;
        private com.google.common.collect.ImmutableMap.Builder<Integer, String> friendMap;

        Person4cBuilder() {
        }

        public Person4cBuilder id(final long id) {
            this.id = id;
            return this;
        }

        public Person4cBuilder name(final String name) {
            this.name = name;
            return this;
        }

        public Person4cBuilder friend(final Integer key, final String value) {
            if (this.friendMap == null) this.friendMap = com.google.common.collect.ImmutableMap.builder();
            this.friendMap.put(key, value);
            return this;
        }

        public Person4cBuilder friendMap(final java.util.Map<? extends Integer, ? extends String> friendMap) {
            if (this.friendMap == null) this.friendMap = com.google.common.collect.ImmutableMap.builder();
            this.friendMap.putAll(friendMap);
            return this;
        }

        public Person4cBuilder clearFriendMap() {
            this.friendMap = null;
            return this;
        }

        public Person4c build() {
            com.google.common.collect.ImmutableMap<Integer, String> friendMap = this.friendMap == null ? com.google.common.collect.ImmutableMap.<Integer, String>of() : this.friendMap.build();
            return new Person4c(id, name, friendMap);
        }

        @Override
        public String toString() {
            return "Person4c.Person4cBuilder(id=" + this.id + ", name=" + this.name + ", friendMap=" + this.friendMap + ")";
        }
    }

    public static Person4cBuilder builder() {
        return new Person4cBuilder();
    }

    @Override
    public String toString() {
        return "Person4c(id=" + this.id + ", name=" + this.name + ", friendMap=" + this.friendMap + ")";
    }
}



関連エントリ