Kotlinでは、Javaのように getX() / setX() を明示的に書くことはほとんどありません。
その理由は、Kotlinが 「プロパティ」 を言語レベルでサポートしているからです。
本記事では、
- Kotlinのgetter / setterが何者なのか
- いつ自動生成され、いつカスタマイズすべきか
- バッキングフィールドの正しい理解
- 実務でよく使われる設計パターン
を正確さ重視で解説します。
目次
Kotlinでは「フィールド」ではなく「プロパティ」が基本概念
まず最も重要な前提です。
var name: String = "Taro"
この1行は、単なる変数宣言ではありません。
Kotlinではこれは プロパティ宣言 であり、以下の要素を含みます。
- 値を保持するための バッキングフィールド
- 値を取得する getter
- 値を変更する setter
※ ただし、これは「この形の場合」に限った話であり、すべてのプロパティが必ずバッキングフィールドを持つわけではありません。
Javaに近い形で表現すると、概念的には次のような構造になります。
private String name = "Taro";
public String getName() {
return name;
}
public void setName(String value) {
name = value;
}
Kotlinではこれらが自動生成されるため、通常は明示的に書く必要がありません。
val と var の違い(getter / setterの有無)
| 宣言 | getter | setter | 再代入 |
|---|---|---|---|
val | あり | なし | 不可 |
var | あり | あり | 可 |
val(読み取り専用プロパティ)
val age: Int = 20
- getterのみを持つ
- 再代入できない
- イミュータブル設計の基本
var(変更可能なプロパティ)
var age: Int = 20
- getter / setter 両方を持つ
- 値の変更が可能
カスタムgetterの基本
フィールドを持たない計算プロパティ
val fullName: String
get() = "山田 太郎"
- バッキングフィールドを持たない
- getterが呼ばれるたびに評価される
- 状態ではなくロジックを表現する用途に適している
バッキングフィールドと field の正しい理解
バッキングフィールドとは?
- プロパティの「実体」となる内部フィールド
- すべてのプロパティに必ず存在するわけではない
field とは?
var price: Int = 1000
get() = field
set(value) {
field = value
}
fieldは アクセサ(getter / setter)内でのみ使える特別な識別子- バッキングフィールドを直接参照するための手段
- プロパティ名(
price)を使うと getter / setter が呼ばれるため、無限再帰になる
NG例(無限再帰)
var price: Int = 1000
get() = price
カスタムsetterと値のバリデーション
基本形
var age: Int = 0
set(value) {
field = value
}
valueは setter に渡される新しい値- 再代入はできない(読み取り専用)
バリデーションの実装例
var age: Int = 0
set(value) {
if (value >= 0) {
field = value
}
}
不正な状態を作らせないための最重要用途
getter / setter の可視性制御
Kotlinでは setterだけを制限する という設計がよく使われます。
var balance: Int = 0
private set
- 外部:読み取り可能
- 内部:変更可能
getterの可視性は通常プロパティと同じで、setterのみをより制限的にするのが一般的です。
Kotlinでは「getterを書かない」のが基本
Java的な発想で次のように書く必要はありません。
fun getName(): String {
return name
}
Kotlinでは、単にこう書きます。
val name: String
Kotlinでは「プロパティとして表現する」こと自体が設計意図です。
バッキングフィールドを持たないプロパティの実例
var width: Int = 0
var height: Int = 0
val area: Int
get() = width * height
areaは値を保持しない- getterが呼ばれるたびに計算される
- 状態の派生結果を安全に表現できる
「生成されない」のは getter / setter ではなく「バッキングフィールド」
誤解されやすいポイントです。
- 計算プロパティでも getterは必ず存在する
- 生成されないのは バッキングフィールド
val area: Int
get() = width * height
この場合
- getter:あり
- バッキングフィールド:なし
Javaとの相互運用における見え方
Kotlinのプロパティは、Javaから見ると次のように見えます。
class User {
var name: String = ""
}
⬇ Java
getName();
setName(String value);
JVMアノテーションの補足
@get:JvmName("getUserName")
var name: String = ""
- getter名を変更できる
- Java APIとして公開する場合のみ慎重に使用する
- 名前衝突を起こしやすいため、設計意図が明確な場合に限定する
実務でよく使われる設計パターン
外部からは変更不可、内部でのみ制御
class User {
var score: Int = 0
private set
fun addScore(point: Int) {
score += point
}
}
- 状態変更を一箇所に集約
- バグ・不正操作を防止
- ViewModel / Domain層で頻出
getter / setter を書くべき判断基準
| ケース | 推奨 |
|---|---|
| 単純な保持 | 自動生成に任せる |
| 値チェックが必要 | カスタムsetter |
| 派生値 | カスタムgetter |
| 外部から変更禁止 | private setter |
| 状態+意味 | プロパティで表現 |
Kotlinらしい結論
- getter / setter は「書く前提」のものではない
- 必要になった瞬間にだけ追加する
- プロパティは「状態」と「意味」を同時に表現できる
- Javaより 安全で、簡潔で、設計意図が伝わりやすい
以上、Kotlinのgetterとsetterについてでした。
最後までお読みいただき、ありがとうございました。










