markdown

はじめに (対象読者・この記事でわかること)

この記事は、Java でのフィールド変数の定義やゲッター/セッターを書いてきた開発者を対象にしています。Kotlin のプロパティ構文を使えば、同等の機能をはるかに簡潔に記述できます。本稿を読むことで、Kotlin の val / var とカスタムアクセサの書き方、そして Java との相互運用方法が理解でき、既存の Java プロジェクトに Kotlin のプロパティを安全に導入できるようになります。Kotlin への移行を検討中の方や、Android 開発で Kotlin を扱う機会が増えている方に最適な入門ガイドです。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • Java の基本的なクラス・フィールド・メソッド定義
  • Kotlin の基礎構文(変数宣言や関数定義)の概要
  • Android Studio または IntelliJ IDEA の基本操作

Kotlin のプロパティとは ― 基本と背景

Java ではフィールドを private にして、外部からは public な getter / setter を提供するのが慣例です。これに対し、Kotlin は プロパティ という概念で同等の機能を統合しています。プロパティは val(読み取り専用)か var(可変)で宣言し、デフォルトで バックフィールド が自動生成され、必要に応じてカスタムアクセサを実装できます。

Kotlin
class User { var name: String = "" // バックフィールドとデフォルト getter/setter が生成 val id: Int = 0 // 読み取り専用、setter が生成されない }

この構文は、冗長なコードを削減し、IDE の自動補完やリファクタリングを容易にします。また、@JvmField アノテーションを付与すれば、Java 側からフィールドに直接アクセスでき、既存の Java コードとの互換性も保てます。

バックフィールドとカスタムアクセサ

デフォルトのアクセサが不十分な場合、field キーワードを使ってバックフィールドに直接アクセスしながらロジックを追加できます。

Kotlin
class Counter { var count: Int = 0 set(value) { if (value >= 0) field = value else throw IllegalArgumentException("Negative") } }

このように、setter 内でバリデーションや副作用を入れられる点は、Java の手動実装と同等かそれ以上の柔軟性を提供します。

Kotlin プロパティの実装手順と Java からの利用例

以下では、実際に Kotlin ファイルにプロパティを定義し、Java から呼び出すまでの流れをステップごとに解説します。

ステップ1 ― Kotlin クラスにプロパティを定義

まずはシンプルなモデルクラスを作成します。ここでは Person クラスに名前と年齢を持たせ、年齢は非負であることを保証します。

Kotlin
// src/main/kotlin/com/example/Person.kt package com.example class Person( var name: String, age: Int ) { var age: Int = age set(value) { require(value >= 0) { "Age must be non‑negative" } field = value } }
  • var name はデフォルトの getter / setter が生成されます。
  • age はカスタム setter でバリデーションを実装。
  • コンストラクタパラメータとして age を受け取り、初期化時にも同じロジックが走ります。

ステップ2 ― Java からのアクセス方法

Kotlin コンパイル後は、Java から通常のフィールドと同様にメソッド呼び出しが可能です。Kotlin のプロパティは getXxx() / setXxx() の形で Java バイトコードに変換されます。

Java
// src/main/java/com/example/Main.java package com.example; public class Main { public static void main(String[] args) { Person person = new Person("Alice", 30); // getter String name = person.getName(); int age = person.getAge(); System.out.println(name + " is " + age + " years old."); // setter (バリデーションが適用される) person.setAge(35); System.out.println("Updated age: " + person.getAge()); // 不正な値を設定しようとすると IllegalArgumentException がスローされる try { person.setAge(-5); } catch (IllegalArgumentException e) { System.out.println("Error: " + e.getMessage()); } } }

ポイントは次の通りです。

Kotlin Java での呼び出し
var name: String person.getName() / person.setName(value)
val id: Int person.getId() (setter なし)
@JvmField 付与 person.name のように直接フィールドアクセス可能

ステップ3 ― @JvmField でフィールド直接公開

Java 側でプロパティをフィールドとして扱いたい場合は、@JvmField を付与します。

Kotlin
class Config { @JvmField var timeout: Long = 5000L }

この場合、Java からは config.timeout と直接アクセスでき、getter / setter が呼び出されません。パフォーマンスが極めて重要なケースや、Android の Parcelable 実装でフィールドを露出させたいときに有用です。

ハマった点やエラー解決

1. Java から val プロパティへ setter を呼び出そうとした

症状: person.setId(10); コンパイルエラー
原因: val は読み取り専用で setter が生成されないため。
解決策: valvar に変更するか、必要ならカスタム getter のみを保持し、setter は提供しない。

2. @JvmField とカスタムアクセサの同時使用

症状: カスタム setter を書いたが、Java からはバリデーションが無視される。
原因: @JvmField を付与すると、Kotlin はフィールド直アクセスを生成し、カスタム accessor が無視される。
解決策: @JvmField を外すか、@JvmSynthetic で内部使用に限定し、外部は getter/setter を使う。

3. Kotlin の field キーワードが未解決

症状: field が認識されないコンパイルエラー。
原因: カスタム accessor の外側で field を使用した。field は自動生成されたバックフィールドへの参照で、setter/getter の内部のみで使用可能。
解決策: アクセサ内部にコードを移動する。

完全コード例

Kotlin
// Kotlin side package com.example class Person( var name: String, age: Int ) { var age: Int = age set(value) { require(value >= 0) { "Age must be non‑negative" } field = value } } // Java side package com.example; public class Demo { public static void main(String[] args) { Person p = new Person("Bob", 25); System.out.println(p.getName() + ", " + p.getAge()); p.setAge(27); System.out.println("Updated: " + p.getAge()); } }

この構成で、Kotlin のプロパティが Java のフィールド/アクセサにシームレスにマッピングされます。

まとめ

本記事では、Kotlin のプロパティ宣言と Java からの利用方法について解説しました。

  • Kotlin の var / val でフィールドとアクセサを一行で定義
  • カスタム setter でバリデーションや副作用を実装
  • @JvmField により Java から直接フィールドアクセスが可能

これにより、Java 開発者でも簡潔かつ安全にデータメンバーを扱えるようになります。今後は、プロパティ委譲やデリゲート、そして Android の ViewModel での活用例についても掘り下げていく予定です。

参考資料