Kotlinのプロパティとコンストラクタについて

採用はこちら

Kotlinでは、クラスのデータを表すものがプロパティであり、その値を初期化する仕組みがコンストラクタです。

Javaでは、フィールドを宣言し、コンストラクタで値を代入し、必要に応じて getter や setter を用意するのが一般的ですが、Kotlinではそれらをもっと簡潔に書けます。

そのため、Kotlinを理解するうえでは、プロパティとコンストラクタを別々に覚えるのではなく、「コンストラクタでプロパティをどう初期化するか」という視点でまとめて理解することが大切です。

目次

プロパティとは何か

プロパティは、クラスやオブジェクトが持つ値を表します。

Kotlinでは、valvar を使ってプロパティを宣言します。

  • val は読み取り専用
  • var は変更可能

たとえば、次のように書けます。

class User {
    val name: String = "Taro"
    var age: Int = 20
}

この場合、name は後から再代入できませんが、age は変更できます。

Kotlinのプロパティは見た目がシンプルですが、単なる公開フィールドではありません。

必要に応じて getter や setter が使われる仕組みになっており、Javaよりも簡潔に書ける一方で、内部的にはもう少し高機能です。

valvar の違い

val は再代入できないプロパティです。

一方、var は再代入できます。

val title: String = "Kotlin入門"
var count: Int = 0

ただし、val は「完全に不変」という意味ではありません。

ここで不変なのは参照そのものです。

たとえば、変更可能なオブジェクトを val で持っている場合、その中身まで固定されるわけではありません。

この点は、Kotlin初心者が誤解しやすいところです。

getter と setter

Kotlinでは、プロパティに対して getter と setter をカスタマイズできます。

getter は値を取得するとき、setter は値を設定するときに呼ばれます。

class Person {
    var age: Int = 0
        set(value) {
            if (value >= 0) {
                field = value
            }
        }
}

ここで使われている field は、そのプロパティの値を保存している内部領域を表します。

setter の中で age = value と書いてしまうと setter 自身を繰り返し呼んでしまうため、値の代入には field を使います。

backing field とは何か

プロパティの値を実際に保存する内部領域をbacking fieldといいます。

ただし、すべてのプロパティに必ず backing field があるわけではありません。

たとえば、次のようなプロパティは、値を保存しているのではなく、アクセスするたびに計算して返しています。

class Product(val price: Int) {
    val priceWithTax: Int
        get() = (price * 1.1).toInt()
}

この priceWithTax は、保持された値を返しているわけではなく、price をもとに毎回計算して返しています。

このようなプロパティは、通常の保存型プロパティとは少し性質が異なります。

主コンストラクタとは何か

Kotlinでは、クラス名の横に書くコンストラクタを主コンストラクタと呼びます。

これは、クラスを初期化するための基本的な仕組みです。

class Person(name: String)

ただし、この name はそのままではプロパティではありません。

ここはとても大事なポイントです。

主コンストラクタの引数に valvar を付けなかった場合、その値はインスタンスに保持されるプロパティにはなりません。

主コンストラクタ引数とプロパティの違い

次のように valvar を付けると、その引数はそのままプロパティになります。

class Person(val name: String, var age: Int)

この場合、nameage はクラスのメンバとして保持されます。

一方、次のように書いた場合は違います。

class Person(name: String)

この name はプロパティではありません。

そのため、外部から person.name のように参照することはできません。

ただし、まったく使えないわけではありません。

主コンストラクタ引数は、プロパティの初期化や init ブロックの中で使えます。

class Person(name: String) {
    val displayName: String = name
}

このように、name は初期化の材料として利用できます。

つまり、val / var のない主コンストラクタ引数は、プロパティにはならないが、初期化時に使う値として扱えると考えると分かりやすいです。

init ブロックとは何か

Kotlinの主コンストラクタはクラスヘッダに書くため、Javaのようにその中へ処理本体を書くことはできません。

その代わり、初期化処理や入力チェックを行いたいときは init ブロック を使います。

class User(val name: String, val age: Int) {
    init {
        require(name.isNotBlank()) { "名前は必須です" }
        require(age >= 0) { "年齢は0以上である必要があります" }
    }
}

init はインスタンス生成時に実行されます。

値の検証や、初期化時に一度だけ行いたい処理を書く場所としてよく使われます。

副コンストラクタとは何か

Kotlinでは、クラス本体の中に constructor を書いて、副コンストラクタを定義できます。

副コンストラクタは、異なる引数パターンでオブジェクトを生成したいときに使います。

class Person {
    var name: String
    var age: Int

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    constructor(name: String) {
        this.name = name
        this.age = 0
    }
}

このようにして、複数の作り方を用意できます。

主コンストラクタがある場合の副コンストラクタ

主コンストラクタがあるクラスでは、副コンストラクタは最終的に this(...) を使って主コンストラクタへ処理を委ねる必要があります。

class Person(val name: String, val age: Int) {
    constructor(name: String) : this(name, 0)
    constructor() : this("Unknown", 0)
}

この流れにより、初期化処理が1か所にまとまり、クラスの状態を一貫して保ちやすくなります。

継承とコンストラクタ

継承を使う場合、派生クラスは基底クラスを正しく初期化しなければなりません。

open class Animal(val name: String)

class Dog(name: String) : Animal(name)

この例では、Dog の主コンストラクタで受け取った name を、そのまま Animal のコンストラクタへ渡しています。

Kotlinの継承では、このように親クラスの初期化を明示する必要があります。

data class と主コンストラクタ

data class では、主コンストラクタ内に定義されたプロパティだけが、自動生成される equals()hashCode()toString()copy()componentN() の対象になります。

data class Person(val name: String) {
    var age: Int = 0
}

この場合、自動生成の対象になるのは name だけです。

age はクラス本体で宣言されているため、equals()copy() には含まれません。

これは実務でも見落としやすいポイントです。

lateinit プロパティ

すぐに初期化できないプロパティには、lateinit を使えることがあります。

これは、「あとで必ず初期化する」という前提で使う仕組みです。

class ServiceHolder {
    lateinit var service: String
}

ただし、lateinit には条件があります。

  • var であること
  • 通常は non-null 型であること
  • 主コンストラクタでは使えないこと
  • custom getter / setter を持てないこと

また、初期化前にアクセスすると例外になります。

const val について

コンパイル時定数には const val を使います。

これは通常の val と違って、使える場所が限られています。

const val APP_NAME = "MyApp"

class Config {
    companion object {
        const val VERSION = "1.0.0"
    }
}

const val は、トップレベル、objectcompanion object の中で使えます。

また、型は String またはプリミティブ型である必要があります。

設定値や固定メッセージなどをまとめるときによく使われます。

実務でよく使う基本形

Kotlinでは、主コンストラクタでそのままプロパティを宣言する形がとてもよく使われます。

class Article(
    val title: String,
    var content: String,
    val author: String
)

この書き方の良いところは、クラスが持つデータの形がすぐ分かることです。

コード量が少なく、見通しも良くなるため、Kotlinらしい書き方としてよく使われます。

学習時に押さえたいポイント

Kotlinのプロパティとコンストラクタを理解するときは、次の点を意識すると混乱しにくくなります。

  1. まず、val / var を付けた主コンストラクタ引数は、そのままプロパティになります。
  2. 一方、val / var を付けない引数はプロパティではありませんが、初期化のための値として使えます。
  3. さらに、複雑な初期化や検証は init に書きます。
  4. そして、data class では主コンストラクタ内のプロパティだけが自動生成の対象になります。

この4点をしっかり押さえるだけでも、理解しやすさがかなり変わります。

まとめ

Kotlinでは、プロパティとコンストラクタは密接につながっています。

valvar でプロパティを宣言し、主コンストラクタで値を受け取り、必要であれば init で初期化や検証を行う、という流れが基本です。

さらに、副コンストラクタ、継承時の初期化、data class の扱い、lateinitconst val まで理解すると、Kotlinのクラス設計がかなり見通しよくなります。

Kotlinは、Javaよりも少ないコードで明確にクラスを書けるのが魅力です。

そのため、まずは主コンストラクタとプロパティの結びつきをしっかり理解することが、上達への近道になります。

以上、Kotlinのプロパティとコンストラクタについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次