覚えたら書く

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

Kotlin - 名前付き引数

Javaで例えば、Book というクラスを定義するとして属性には以下を持つものとします。

  • Book
    • title : 本のタイトル
    • author : 著者
    • price : 価格(円)
    • isbn : ISBN-13
    • remarks : 備考。(あれば何か書く。特に無いかもしれない)

これを単純にJavaのクラスとして表現すると以下の様になります。

public final class Book {

    private final String title;

    private final int price;

    private final String author;

    private final String isbn;

    private final String remarks;

    public Book(String title, int price, String author, String isbn, String remarks) {
        this.title = title;
        this.price = price;
        this.author = author;
        this.isbn = isbn;
        this.remarks = remarks;
    }

    public String getTitle() {
        return title;
    }

    public int getPrice() {
        return price;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

    public String getRemarks() {
        return remarks;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                ", isbn='" + isbn + '\'' +
                ", remarks='" + remarks + '\'' +
                '}';
    }
}

これを呼び出すコードを書くと以下の様になります

Book book =
        new Book("Kotlinイン・アクション", 4115, "Dmitry Jemerov", "978-4839961749", "サイズ: 23.6x18.2x3.6 cm");

System.out.println(book);

◾️実行結果

Book{title='Kotlinイン・アクション', price=4115, author='Dmitry Jemerov', isbn='978-4839961749', remarks='サイズ: 23.6x18.2x3.6 cm'}


コンストラクタの引数が多すぎて、与えてるパラメータの順番が正しいのかが呼び出し側のコードからは良く分かりません。


こういったケースでは、一般的にBuilderパターンを利用して以下の様に書きます。

public final class Book {

    private final String title;

    private final int price;

    private final String author;

    private final String isbn;

    private final String remarks;

    private Book(Builder builder) {
        this.title = builder.title;
        this.price = builder.price;
        this.author = builder.author;
        this.isbn = builder.isbn;
        this.remarks = builder.remarks;
    }

    public String getTitle() {
        return title;
    }

    public int getPrice() {
        return price;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

    public String getRemarks() {
        return remarks;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                ", isbn='" + isbn + '\'' +
                ", remarks='" + remarks + '\'' +
                '}';
    }

    public static class Builder {
        private String title;
        private int price;
        private String author;
        private String isbn;
        private String remarks;

        Builder() {
        }

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

        public Builder price(int price) {
            this.price = price;
            return this;
        }

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

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

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

        public Book build() {
            return new Book(this);
        }
    }

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

これを呼び出すコードは以下の様になり、当初のものと比較して何のパラメータを与えているのかが明確になります

Book book = Book.builder()
        .title("Kotlinイン・アクション")
        .author("Dmitry Jemerov")
        .price(4115)
        .isbn("978-4839961749")
        .remarks("サイズ: 23.6x18.2x3.6 cm")
        .build();

System.out.println(book);  // ->  結果はもとのものと同じ

Builderを登場させる事で、呼び出し時に分かりやすさが確保されるのですが、
いかんせんコードが長くなります。いわゆるBuilderクラスを登場させるがゆえのボイラープレートコードが増えたと言えます。

これを楽にするためには、Lombok の @Builderを利用する方法などがあります。


名前付き引数

Kotlin には関数呼び出し時に、その引数の名前を指定する事が可能です。

KotlinでBookクラスを定義して呼び出すと以下の様になります。

class Book(
    val title: String,
    val price: Int,
    val author: String,
    val isbn: String,
    val remarks: String
) {
    override fun toString(): String {
        return "Book(title='$title', price=$price, author='$author', isbn='$isbn', remarks='$remarks')"
    }
}


呼び出し側のコード

val book = Book(
    title = "Kotlinイン・アクション",
    price = 4115,
    author = "Dmitry Jemerov",
    isbn = "978-4839961749",
    remarks = "サイズ: 23.6x18.2x3.6 cm")

println(book)

◾️実行結果

Book(title='Kotlinイン・アクション', price=4115, author='Dmitry Jemerov', isbn='978-4839961749', remarks='サイズ: 23.6x18.2x3.6 cm')

Builderを持ち出す事なく、明確なパラメータの設定でBookのインスタンス生成ができています。


デフォルト引数

Kotlinでは関数宣言で引数にデフォルト値を指定可能です。

先ほど定義したBookの remarks のデフォルト値を ''-" にしてみます。

class Book(
    val title: String,
    val price: Int,
    val author: String,
    val isbn: String,
    val remarks: String = "-"
) {
    override fun toString(): String {
        return "Book(title='$title', price=$price, author='$author', isbn='$isbn', remarks='$remarks')"
    }
}

呼び出し側のコードは以下の通りで、remarksを明示的に指定するケースと指定しないケースの2パターンで呼び出します。

val book1 = Book(
    title = "Kotlinイン・アクション",
    price = 4115,
    author = "Dmitry Jemerov",
    isbn = "978-4839961749",
    remarks = "サイズ: 23.6x18.2x3.6 cm")

println(book1)


val book2 = Book(
    title = "Kotlinイン・アクション",
    price = 4115,
    author = "Dmitry Jemerov",
    isbn = "978-4839961749")

println(book2)

◾️実行結果

Book(title='Kotlinイン・アクション', price=4115, author='Dmitry Jemerov', isbn='978-4839961749', remarks='サイズ: 23.6x18.2x3.6 cm')
Book(title='Kotlinイン・アクション', price=4115, author='Dmitry Jemerov', isbn='978-4839961749', remarks='-')

いずれのケースも正常に動作し、remarksを指定しなかった場合デフォルト値が利用されている事が分かります。


Java にはメソッド等に、このデフォルト値を持たせる機構がないため、オーバーロードで代替する必要があります。
が、オーバーロードメソッドが増えすぎると、メソッドごとの違いが良くわからなくなってきて混乱の元になります。

Kotlinのデフォルト引数はその様な混乱を無くしてくれるものとなります。



関連エントリ