0Pricing
Kotlin Academy · レッスン

リフレクションベースのバリデーションとマッピング

アノテーション駆動のリフレクションを使い、シンプルなバリデーターまたはオブジェクトマッパーを構築します。

「リフレクションベースのバリデーションとマッピング」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。

このパターンで解決できること

リフレクションベースのバリデーションでは、実行時にクラスのプロパティからアノテーションを読み取り、各フィールドを手動でチェックすることなくバリデーションルールを自動的に適用します。同じ技術は、オブジェクトとデータベースの行を相互変換するORMマッパーにも利用されています。

Validateアノテーション

実行時まで保持され、プロパティを対象とするアノテーションを定義します。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Validate(
    val minLength: Int = 0,
    val maxLength: Int = Int.MAX_VALUE,
    val pattern: String = ""
)

データクラスへのアノテーション付与

データクラスのプロパティに@Validateを付与します。バリデータは実行時にこれらを検査します。

data class UserForm(
    @Validate(minLength = 2, maxLength = 50) val name: String,
    @Validate(pattern = "^[\\w.]+@[\\w]+\\.[a-z]{2,}$") val email: String,
    @Validate(minLength = 8) val password: String
)

バリデータ関数

KClass.declaredMemberPropertiesを使ってプロパティを反復処理し、アノテーションを見つけてエラーを収集します。

fun validate(obj: Any): List<String> {
    val errors = mutableListOf<String>()
    for (prop in obj::class.declaredMemberProperties) {
        val ann = prop.findAnnotation<Validate>() ?: continue
        val value = (prop as KProperty1<Any, *>).get(obj)?.toString() ?: ""
        if (value.length < ann.minLength) errors += "${prop.name}: too short"
        if (value.length > ann.maxLength) errors += "${prop.name}: too long"
        if (ann.pattern.isNotEmpty() && !value.matches(Regex(ann.pattern)))
            errors += "${prop.name}: invalid format"
    }
    return errors
}

バリデータの実行

アノテーションが付与された任意のオブジェクトをvalidate()に渡します。この関数はエラーメッセージのリストを返し、空のリストは有効であることを示します。

val form = UserForm("A", "not-an-email", "short")
val errors = validate(form)
errors.forEach(::println)
// name: too short
// email: invalid format
// password: too short

@ColumnによるオブジェクトからMapへのマッピング

別のリフレクションパターンでは、@Columnアノテーションを使ってカラム名を指定し、オブジェクトをMapにマッピングします。

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class Column(val name: String)

fun toMap(obj: Any): Map<String, Any?> =
    obj::class.declaredMemberProperties
        .mapNotNull { prop ->
            val col = prop.findAnnotation<Column>() ?: return@mapNotNull null
            col.name to (prop as KProperty1<Any, *>).get(obj)
        }.toMap()

マッパーの使用

データクラスにアノテーションを付与し、toMap()を呼び出すと、INSERT文にそのまま使える、カラムをキーとするマップを取得できます。

data class User(
    @Column("user_name") val name: String,
    @Column("user_email") val email: String
)

val user = User("Alice", "alice@example.com")
println(toMap(user))
// {user_name=Alice, user_email=alice@example.com}

リフレクション結果のキャッシュ

リフレクションによるスキャンにはコストがかかります。KClassをキーとするConcurrentHashMapを使い、プロパティとアノテーションの対応関係を起動時にキャッシュします。

val cache = ConcurrentHashMap<KClass<*>, List<Pair<KProperty1<Any,*>, Column>>>()

fun columnsOf(klass: KClass<*>) = cache.getOrPut(klass) {
    klass.declaredMemberProperties
        .mapNotNull { p ->
            p.findAnnotation<Column>()?.let { (p as KProperty1<Any,*>) to it }
        }
}

バリデーションとマッピングの組み合わせ

1つのプロパティに複数のアノテーションを重ねて付与し、それぞれを独立して処理できます。まずバリデーションを行ってからマッピングすることも、各アノテーション型を収集して1回の走査で両方を行うこともできます。

data class Product(
    @Validate(minLength = 1) @Column("product_name") val name: String,
    @Column("price") val price: Double
)

制限事項

リフレクションベースのバリデーションは強力ですが、トレードオフがあります。実行時のオーバーヘッドが増え、アノテーションの保持設定に依存し、コンパイル時にはエラーを検出できません。パフォーマンスが重要な場合やコンパイル時の安全性が必要な場合は、KSPで生成したコードを検討してください。

このパターンを使う場面

リフレクションベースのバリデーションは、ORM、シリアライザ、フォームバリデータ、設定バインダーなど、任意のユーザー定義クラスを扱う必要があるフレームワークコードで特に有効です。型を自分で管理しているアプリケーションコードでは、速度とわかりやすさのために明示的なバリデーションを優先してください。

確認問題

バリデーション用アノテーションを実行時に読み取るには、どのアノテーション保持ポリシーが必要ですか。

まとめ:リフレクションベースのバリデーションとマッピング

重要なポイント:

  • 実行時まで保持され、プロパティを対象とするアノテーションを宣言します
  • declaredMemberPropertiesとfindAnnotationを使ってバリデーションやマッピングを行います
  • リフレクションのオーバーヘッドを繰り返し発生させないよう、KClassごとに結果をキャッシュします
  • 1つのプロパティに複数のアノテーションを重ねて付与し、複合的な動作を実現します
  • パフォーマンスが重要な処理では、コンパイル時の代替手段としてKSPを検討します

よくある質問

「リフレクションベースのバリデーションとマッピング」レッスンは無料ですか?

はい。「リフレクションベースのバリデーションとマッピング」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。

「リフレクションベースのバリデーションとマッピング」で何を学びますか?

アノテーション駆動のリフレクションを使い、シンプルなバリデーターまたはオブジェクトマッパーを構築します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Kotlin Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「リフレクションベースのバリデーションとマッピング」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このKotlin Academyレッスンでコードを書いて実行できますか?

はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Kotlin Reflection:KClass、KFunction、KProperty
  2. カスタムアノテーションの作成と対象指定
  3. 実行時にアノテーションを読み取る
  4. リフレクションベースのバリデーションとマッピング
← Kotlin Academyに戻る