Kotlinでアノテーションに引数を渡す方法を徹底解説

Kotlinでは、アノテーションがプログラムのメタデータとして機能し、コンパイラやランタイムで特定の動作を制御する重要な役割を果たします。例えば、コード生成やデータの検証、APIの仕様を記述する場面で頻繁に使用されます。これに加え、アノテーションに引数を渡すことで、柔軟な設定や動作を指定することが可能です。本記事では、Kotlinにおけるアノテーションの基礎から、引数を渡す具体的な方法、さらには実践的な応用例までを詳細に解説します。これを通じて、Kotlin開発におけるアノテーション活用の幅を広げていただけるでしょう。

目次

アノテーションとは?


アノテーションとは、ソースコードに付加情報を提供するための仕組みで、プログラムの動作や構成要素に特定のメタデータを埋め込む手段です。これにより、コードの振る舞いや設定を柔軟に制御できます。

アノテーションの基本的な役割


アノテーションは、以下のような役割を持ちます。

  • コンパイラ指示: コンパイラに特定の指示を与える(例: 非推奨なAPIの使用を警告する@Deprecated)。
  • ランタイム処理: 実行時にリフレクションを通じて特定の処理をトリガーする(例: データバインディングや依存性注入)。
  • コード生成: ライブラリやツールがソースコードから補助コードを自動生成する。

Kotlinでのアノテーションの基本例


以下はKotlinのアノテーションを簡単に示した例です。

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ExampleAnnotation(val description: String)

このアノテーションをクラスに適用すると、以下のようになります。

@ExampleAnnotation(description = "This is a sample class")
class SampleClass

アノテーションの利用が広がる場面


アノテーションは次のようなシーンで広く使用されています。

  • テストフレームワーク(例: @Test
  • データバインディング(例: @SerializedName
  • REST APIの設定(例: @GET@POST

これらを理解することで、アノテーションの持つ柔軟性と利便性を実感できるでしょう。

Kotlinにおけるアノテーションの特徴

Kotlinのアノテーションは、Javaのアノテーションを基にしながらも、Kotlin固有の特性を備えており、より簡潔かつ直感的に利用できます。以下では、Kotlinにおけるアノテーションの特徴を詳しく解説します。

1. 宣言の簡潔さ


Kotlinでは、アノテーションの宣言が非常にシンプルです。例えば、引数を持つアノテーションも簡単に定義できます。

annotation class MyAnnotation(val message: String, val priority: Int)

上記の例では、messagepriorityという引数を持つアノテーションを定義しています。

2. ターゲットとリテンションの設定


Kotlinでは、アノテーションの適用対象(ターゲット)や保持期間(リテンション)を指定できます。

  • ターゲット: アノテーションが適用可能な要素を限定します(例: クラス、関数、プロパティなど)。
  • リテンション: アノテーション情報をどの段階まで保持するかを指定します(例: コンパイル時のみ、ランタイムまで保持)。
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Logging

この例では、Loggingアノテーションが関数にのみ適用可能で、ランタイムまで保持されるように指定されています。

3. Kotlin特有の追加ターゲット


Kotlinでは、AnnotationTargetに以下のようなJavaにはない独自のターゲットがあります。

  • PROPERTY: プロパティ全体に適用する。
  • PROPERTY_GETTER/PROPERTY_SETTER: プロパティのゲッターやセッターに限定して適用する。
  • FILE: ファイル全体に適用する。
@Target(AnnotationTarget.PROPERTY)
annotation class Validate

4. アノテーションの引数にKotlin型を使用


Kotlinのアノテーションでは、以下の型を引数として利用できます。

  • プリミティブ型(例: Int, Double)
  • String
  • Enumクラス
  • 他のアノテーション
  • 配列

ただし、可変引数やジェネリクスなどの動的な構造はサポートされていません。

5. KotlinのNull安全との統合


Kotlinでは、アノテーションの引数としてnullable型(String?など)を使用することはできません。この制約により、アノテーションのデータが常に有効な値を持つことが保証されます。

6. Javaとの相互運用性


Kotlinで定義したアノテーションは、Javaコードでも使用可能です。また、JavaのアノテーションもKotlinコード内で問題なく利用できます。この相互運用性により、既存のJavaライブラリやフレームワークとシームレスに連携できます。


これらの特徴を活用することで、Kotlinのアノテーションはより強力で表現力豊かなツールとして活躍します。次章では、アノテーションに引数を渡す具体的な方法について掘り下げていきます。

アノテーションに引数を渡す基本構文

Kotlinでは、アノテーションに引数を渡すことで、追加のメタデータや設定を指定できます。引数付きのアノテーションは、コードの柔軟性を高め、動作をカスタマイズするために利用されます。ここでは基本構文を詳しく解説します。

1. 引数付きアノテーションの定義


アノテーションを定義する際に、引数を指定するには、クラスのコンストラクタに相当する形式で引数を記述します。

annotation class MyAnnotation(val name: String, val age: Int)

上記の例では、nameageという2つの引数を持つアノテーションを定義しています。

2. アノテーションに引数を渡す方法


アノテーションを使用する際に、定義した引数に値を渡します。

@MyAnnotation(name = "Alice", age = 30)
class Person

この例では、name"Alice"age30を指定してPersonクラスにアノテーションを適用しています。

3. 引数のデータ型


Kotlinのアノテーションに渡せる引数のデータ型は以下に限定されています。

  • プリミティブ型(Int, Double, Boolean など)
  • String
  • 列挙型(enum
  • 他のアノテーション
  • 配列

例:

annotation class Config(val version: String, val active: Boolean)
@Config(version = "1.0", active = true)
class AppSettings

4. 配列を引数として渡す


複数の値を持つ配列を引数として渡すことも可能です。その際、KotlinではarrayOfを使用します。

annotation class Tags(val tags: Array<String>)
@Tags(tags = arrayOf("Kotlin", "Annotation", "Tutorial"))
class TaggedClass

5. 他のアノテーションを引数に渡す


アノテーションを引数として使用することもできます。

annotation class Role(val roleName: String)
annotation class User(val roles: Array<Role>)
@User(roles = [Role("Admin"), Role("Editor")])
class AdminUser

6. デフォルト値の設定


アノテーションの引数にデフォルト値を設定することで、省略時の動作を定義できます。

annotation class Config(val version: String = "1.0", val debug: Boolean = false)
@Config
class DefaultConfig

この例では、Configアノテーションのversiondebugの引数が省略され、デフォルト値が使用されます。

7. 注意点

  • 引数にnullを指定することはできません(Kotlinのアノテーションはnull安全ではないため)。
  • Kotlin独自の型やコレクション(ListMapなど)を引数に使用することはできません。

これらの基本構文を押さえることで、Kotlinのアノテーションを効果的に利用し、柔軟なプログラムを構築する基礎を学ぶことができます。次章では、プリミティブ型を引数に持つ具体的な例を掘り下げていきます。

プリミティブ型の引数を持つアノテーション

Kotlinのアノテーションでは、IntDoubleBooleanといったプリミティブ型を引数として使用できます。これにより、簡潔かつ効率的にメタデータを記述でき、柔軟な制御が可能です。以下に、プリミティブ型を引数に持つアノテーションの具体例を解説します。

1. プリミティブ型引数の基本例


アノテーションにプリミティブ型の引数を定義し、使用する例です。

annotation class Priority(val level: Int, val active: Boolean)

@Priority(level = 5, active = true)
class ImportantTask

この例では、Priorityアノテーションのlevelに整数値、activeに真偽値を指定しています。この設定により、ImportantTaskクラスが優先度5で有効なタスクとしてマークされます。

2. 複数のプリミティブ型を組み合わせる


複数のプリミティブ型を組み合わせてアノテーションを設計することで、より詳細な情報を提供できます。

annotation class Metrics(val value: Double, val threshold: Int)

@Metrics(value = 95.5, threshold = 100)
class PerformanceMonitor

この例では、Metricsアノテーションで測定値と閾値を指定しています。これにより、性能モニタリングクラスの設定を簡潔に定義できます。

3. デフォルト値を利用する


プリミティブ型の引数にデフォルト値を設定することで、柔軟性が向上します。

annotation class Retry(val attempts: Int = 3, val delay: Long = 1000)

@Retry
class NetworkRequest

この例では、Retryアノテーションのattemptsdelayにデフォルト値が設定されています。省略時には3回のリトライと1秒の遅延が適用されます。

4. 配列型としてのプリミティブ型引数


プリミティブ型の配列を引数に渡すことも可能です。

annotation class Ranges(val values: IntArray)

@Ranges(values = [1, 5, 10])
class ValueChecker

この例では、Rangesアノテーションに整数値の配列を渡しています。ValueCheckerクラスでは、この配列を利用して特定の範囲内の値をチェックすることができます。

5. 使用例: テストフレームワークでの活用


プリミティブ型引数を用いるアノテーションは、テストフレームワークで特に便利です。例えば、テストケースの優先順位を指定する場合などに使われます。

annotation class TestCase(val id: Int, val priority: Int = 1)

@TestCase(id = 101, priority = 3)
fun criticalTest() {
    // テストコード
}

この例では、TestCaseアノテーションを使用して、テストケースのIDと優先順位を指定しています。

6. 注意点

  • プリミティブ型は直接扱えるため、コンパイル時のオーバーヘッドが少なく、高速に処理されます。
  • Null値を許容しないため、必須項目として活用しやすい一方、意図的にデータがない状態を表現する場合は工夫が必要です(デフォルト値などを活用)。

プリミティブ型の引数は、アノテーションを使ったプログラム設計においてシンプルで効果的な手段です。次章では、配列やカスタム型を引数として使用する方法について掘り下げていきます。

配列やカスタム型を引数として渡す方法

Kotlinでは、アノテーションに配列やカスタム型を引数として渡すことができます。これにより、複数の値や柔軟なデータをアノテーションで表現でき、より複雑な設定や情報を記述することが可能です。ここでは具体的な例とともに、配列やカスタム型を引数に利用する方法を解説します。

1. 配列を引数として渡す方法


Kotlinでは、配列型の引数を定義する際にArray<T>やプリミティブ型専用の配列型(例: IntArray)を使用します。

annotation class Roles(val roles: Array<String>)

@Roles(roles = ["Admin", "Editor", "Viewer"])
class UserAccessControl

この例では、Rolesアノテーションのroles引数に文字列の配列を渡しています。このアノテーションを使用することで、UserAccessControlクラスが複数のユーザー権限を持つことを示しています。

2. プリミティブ型の配列


配列の要素がプリミティブ型の場合、IntArrayDoubleArrayなどの専用配列型を使用します。

annotation class Thresholds(val values: IntArray)

@Thresholds(values = [10, 20, 30])
class SensorLimits

この例では、Thresholdsアノテーションに整数の配列を渡し、複数の閾値を定義しています。

3. カスタム型を引数として渡す方法


アノテーションの引数として、列挙型や他のアノテーションなどのカスタム型を使用することができます。

3.1 列挙型を引数に使用


列挙型を引数として渡すことで、固定された選択肢を指定できます。

enum class PriorityLevel { LOW, MEDIUM, HIGH }

annotation class Task(val priority: PriorityLevel)

@Task(priority = PriorityLevel.HIGH)
class CriticalTask

この例では、列挙型PriorityLevelを引数に持つTaskアノテーションを使用し、CriticalTaskクラスの優先度を指定しています。

3.2 他のアノテーションを引数に使用


アノテーションを引数にすることで、ネストされたメタデータを表現できます。

annotation class Permission(val role: String)
annotation class AccessControl(val permissions: Array<Permission>)

@AccessControl(permissions = [Permission("Admin"), Permission("User")])
class SecureResource

この例では、AccessControlアノテーションに複数のPermissionアノテーションを渡すことで、複雑なアクセス制御を表現しています。

4. 配列やカスタム型の実践例


アノテーションで配列やカスタム型を利用する場面の実践的な例を示します。

4.1 配列でのタグ付け

annotation class Tags(val tags: Array<String>)

@Tags(tags = ["Kotlin", "Annotation", "Tutorial"])
class Article

この例では、記事に複数のタグを付ける際にTagsアノテーションを使用しています。

4.2 カスタム型での複数ロールの指定

annotation class Role(val name: String)
annotation class UserRoles(val roles: Array<Role>)

@UserRoles(roles = [Role("Admin"), Role("Viewer")])
class Dashboard

この例では、ダッシュボードにアクセス可能な複数のユーザー権限を指定しています。

5. 注意点

  • 配列の初期化にはarrayOf()または専用配列型の構文を使用する必要があります。
  • カスタム型として渡せるのは列挙型や他のアノテーションに限られ、Kotlin独自のコレクション型(例: ListMap)はサポートされていません。

配列やカスタム型を引数に活用することで、アノテーションをさらに柔軟かつ表現力豊かに使用できます。次章では、アノテーション引数にデフォルト値を設定する方法について詳しく解説します。

アノテーションの引数にデフォルト値を設定する方法

Kotlinでは、アノテーションの引数にデフォルト値を設定することができます。これにより、アノテーションを簡潔に記述でき、すべての引数を指定する必要がなくなるため、コードの可読性や保守性が向上します。ここではデフォルト値の設定方法とその応用例について解説します。

1. デフォルト値の基本構文


アノテーションの引数にデフォルト値を設定するには、引数の定義時に初期値を指定します。

annotation class Config(val version: String = "1.0", val debug: Boolean = false)

この例では、versionには"1.0"debugにはfalseというデフォルト値が設定されています。

2. デフォルト値を利用したアノテーションの使用例


デフォルト値を持つアノテーションでは、必要な引数のみを指定することができます。

@Config
class DefaultConfig

@Config(version = "2.0")
class CustomConfig

この例では、DefaultConfigクラスにはデフォルト値が適用され、CustomConfigクラスではversionが上書きされています。

3. 配列型の引数にデフォルト値を設定


配列型の引数にもデフォルト値を設定することが可能です。

annotation class Tags(val tags: Array<String> = ["default"])

@Tags
class DefaultTags

@Tags(tags = ["Kotlin", "Programming"])
class CustomTags

この例では、DefaultTagsクラスに"default"タグが付けられ、CustomTagsクラスには指定されたカスタムタグが適用されています。

4. 列挙型引数にデフォルト値を設定


列挙型を引数に持つ場合も、デフォルト値を設定することが可能です。

enum class LogLevel { DEBUG, INFO, WARN, ERROR }

annotation class Logger(val level: LogLevel = LogLevel.INFO)

@Logger
class DefaultLogger

@Logger(level = LogLevel.ERROR)
class ErrorLogger

この例では、DefaultLoggerクラスではデフォルトでINFOレベルが適用され、ErrorLoggerクラスでは指定されたERRORレベルが使用されています。

5. 他のアノテーションをデフォルト値として使用


他のアノテーションを引数として持つ場合、そのデフォルト値も設定可能です。

annotation class Permission(val role: String = "User")
annotation class Access(val permission: Permission = Permission())

@Access
class DefaultAccess

@Access(permission = Permission(role = "Admin"))
class AdminAccess

この例では、DefaultAccessクラスには"User"というデフォルトのロールが割り当てられ、AdminAccessクラスでは"Admin"が指定されています。

6. デフォルト値を利用するメリット

  • 簡潔さ: デフォルト値を設定することで、必要最小限の引数のみを指定すればよくなります。
  • 保守性: デフォルトの設定があることで、アノテーションの利用者がすべての詳細を理解しなくても利用可能です。
  • 拡張性: 新しい引数を追加しても、デフォルト値を設定すれば既存のコードに影響を与えずに拡張できます。

7. 注意点

  • デフォルト値としてnullを指定することはできません。Kotlinでは、アノテーション引数にnull値を許容しないため、必須ではないデータはデフォルト値で表現する必要があります。
  • 配列型やカスタム型のデフォルト値も静的に定義する必要があります。動的な計算結果をデフォルト値として使用することはできません。

デフォルト値を活用することで、アノテーションの使用がより簡潔かつ柔軟になります。次章では、アノテーションの引数に関連するコンパイル時の制約と注意点について解説します。

コンパイル時の制約と注意点

Kotlinでアノテーションを使用する際には、コンパイル時に特定の制約が適用されます。これらの制約を理解することで、エラーを未然に防ぎ、アノテーションの設計や使用を適切に行うことができます。本章では、アノテーションの引数に関連する主要なコンパイル時の制約と注意点を詳しく解説します。

1. 許可される引数の型の制約


Kotlinでは、アノテーションの引数に使用できる型が厳密に限定されています。以下の型のみがサポートされます。

  • プリミティブ型(Int, Boolean, Double など)
  • String
  • 列挙型(enum
  • 他のアノテーション
  • 配列(上記の型のみを要素として持つ)

これら以外の型(ListMap、ユーザー定義クラスなど)は使用できません。

// 許可されない例
annotation class Invalid(val data: List<String>) // コンパイルエラー

2. デフォルト値は定数式である必要がある


アノテーションの引数にデフォルト値を指定する場合、その値はコンパイル時に定まる定数でなければなりません。動的な計算やランタイムで決定される値を使用することはできません。

annotation class Example(val value: Int = 10) // 有効
annotation class InvalidExample(val value: Int = calculate()) // コンパイルエラー

fun calculate(): Int {
    return 42
}

3. Null値の非許容


Kotlinのアノテーションでは、引数にnull値を指定することができません。そのため、nullを扱う必要がある場合は、デフォルト値や特殊な値(例: 空文字列や-1)を活用する必要があります。

annotation class Example(val name: String = "") // 空文字列を利用

4. 配列引数の初期化


配列を引数に指定する場合、arrayOf()やリテラル構文を使用して初期化する必要があります。

annotation class Tags(val values: Array<String>)

@Tags(values = arrayOf("Tag1", "Tag2")) // 有効
@Tags(values = ["Tag1", "Tag2"]) // ショートカット構文(有効)

プリミティブ型の配列には専用の構文(例: intArrayOf())を使用します。

5. アノテーションのターゲットと使用可能範囲


アノテーションには適用可能なターゲット(例: クラス、関数、プロパティなど)が指定されています。間違ったターゲットに適用するとコンパイルエラーが発生します。

@Target(AnnotationTarget.FUNCTION)
annotation class FunctionOnly

@FunctionOnly
class InvalidUsage // コンパイルエラー

6. カスタム型の制限


カスタム型(列挙型や他のアノテーション)を引数として利用する場合、型定義が適切であることを保証する必要があります。特に、列挙型の値やアノテーションの構造が固定されている必要があります。

enum class Role { ADMIN, USER }

annotation class Access(val role: Role = Role.USER)

@Access(role = Role.ADMIN) // 有効

7. リフレクションとリテンションの制約


アノテーションの引数はリフレクションで取得できますが、リフレクションを利用するにはRetentionRUNTIMEである必要があります。

@Retention(AnnotationRetention.RUNTIME)
annotation class Example(val value: String)

fun main() {
    val annotation = Example::class.annotations.find { it is Example } as? Example
    println(annotation?.value) // "RUNTIME"が指定されていない場合はnullになる
}

8. その他の注意点

  • アノテーションの引数は不変であり、ランタイム中に変更することはできません。
  • 引数が複数ある場合、それらをすべて明示的に指定するか、デフォルト値を利用する必要があります。

これらの制約を正しく理解することで、コンパイルエラーを回避し、アノテーションの設計を効率的に行うことができます。次章では、実際のプロジェクトにおけるアノテーションの応用例について解説します。

実践的な応用例

Kotlinでアノテーションを使用する際の実践的な応用例を紹介します。これらの例を通じて、アノテーションを活用した効率的な開発手法や、現実のプロジェクトでの具体的な利用方法を理解できます。

1. データバリデーション


アノテーションを利用してデータのバリデーションを簡素化する方法を示します。

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class MinLength(val length: Int)

data class User(
    @MinLength(5) val username: String,
    @MinLength(8) val password: String
)

fun validate(obj: Any) {
    val clazz = obj::class
    for (property in clazz.members) {
        val annotation = property.annotations.find { it is MinLength } as? MinLength
        if (annotation != null) {
            val value = property.call(obj) as? String
            if (value != null && value.length < annotation.length) {
                throw IllegalArgumentException("${property.name} must be at least ${annotation.length} characters long")
            }
        }
    }
}

fun main() {
    val user = User(username = "john", password = "pass")
    validate(user) // IllegalArgumentExceptionが発生
}

この例では、プロパティにアノテーションを付与し、リフレクションを使用してバリデーションを自動化しています。

2. APIエンドポイントの定義


REST APIエンドポイントの定義をアノテーションで簡略化できます。

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Get(val path: String)

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Post(val path: String)

class ApiController {
    @Get("/users")
    fun getUsers() {
        println("Fetching users")
    }

    @Post("/users")
    fun createUser() {
        println("Creating user")
    }
}

フレームワークやツールと組み合わせることで、ルーティング処理を自動化することができます。

3. デフォルト設定の注入


アノテーションを利用してクラスにデフォルト設定を注入する例です。

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Config(val environment: String = "development", val version: String = "1.0")

@Config(environment = "production", version = "2.0")
class AppConfig

fun loadConfig(clazz: KClass<*>): Map<String, String> {
    val annotation = clazz.annotations.find { it is Config } as? Config
    return mapOf(
        "environment" to (annotation?.environment ?: "default"),
        "version" to (annotation?.version ?: "unknown")
    )
}

fun main() {
    val config = loadConfig(AppConfig::class)
    println(config) // {"environment":"production", "version":"2.0"}
}

この例では、アノテーションを使用して環境設定やバージョン情報をクラスに埋め込んでいます。

4. 権限管理


ユーザー権限をアノテーションで管理し、処理の柔軟性を向上させます。

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class RequiresRole(val role: String)

class SecureService {
    @RequiresRole("Admin")
    fun sensitiveOperation() {
        println("Performing sensitive operation")
    }
}

fun invokeIfAuthorized(obj: Any, methodName: String, userRole: String) {
    val method = obj::class.members.find { it.name == methodName }
    val annotation = method?.annotations?.find { it is RequiresRole } as? RequiresRole
    if (annotation != null && annotation.role != userRole) {
        throw SecurityException("Access denied: Requires role ${annotation.role}")
    }
    method?.call(obj)
}

fun main() {
    val service = SecureService()
    invokeIfAuthorized(service, "sensitiveOperation", "Admin") // 実行可能
    invokeIfAuthorized(service, "sensitiveOperation", "User")  // SecurityException
}

この例では、メソッドに権限情報をアノテーションで埋め込み、実行時にチェックしています。

5. シリアライズとデシリアライズ


アノテーションを活用してJSONのシリアライズとデシリアライズをカスタマイズします。

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

data class Person(
    @JsonName("full_name") val name: String,
    @JsonName("age_years") val age: Int
)

fun serialize(obj: Any): String {
    val clazz = obj::class
    val json = clazz.members.filter { it.annotations.any { it is JsonName } }
        .joinToString(prefix = "{", postfix = "}") { member ->
            val annotation = member.annotations.find { it is JsonName } as JsonName
            val value = member.call(obj)
            "\"${annotation.name}\":\"$value\""
        }
    return json
}

fun main() {
    val person = Person(name = "Alice", age = 25)
    println(serialize(person)) // {"full_name":"Alice","age_years":"25"}
}

この例では、アノテーションを使用してJSONのキー名を指定しています。


これらの応用例を基に、Kotlinでのアノテーションの可能性をさらに拡張することができます。次章では、今回の内容を総括し、アノテーションの活用ポイントを振り返ります。

まとめ

本記事では、Kotlinにおけるアノテーションの引数に関する基本的な使い方から、応用的な実践例までを詳細に解説しました。アノテーションは、コードのメタデータとして機能し、柔軟かつ効率的なプログラム設計を可能にします。特に、引数を渡すことでアノテーションの機能を強化し、データバリデーション、設定注入、権限管理など幅広い用途に活用できることを学びました。

アノテーションを適切に利用することで、コードの簡潔さと可読性が向上し、保守性の高い設計を実現できます。Kotlinの豊富な機能を活かして、プロジェクトに最適なアノテーションを設計し、開発効率をさらに高めていきましょう。

コメント

コメントする

目次