2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Kotlinの入門

Last updated at Posted at 2021-12-24

Kotlin 入門

Kotlinとは

  • Kotlinとは、Javaの統合開発環境であるIntelliJ IDEAで有名なJetBrainsが開発したオブジェクト指向プログラミング言語
    • Groovy やScala から、機能や簡易記法(糖衣構文)を採用している
    • ジェネリクスの構文などでC#の影響を受けている
    • 2012年2月14日、Kotlin はApacheライセンス バージョン2.0に基づいてオープンソース化された
    • 2019年、GoogleはKotlinをAndroidアプリケーション開発の標準にした
  • 言語構文自体はJavaとは互換性がないが、コンパイルされたコードはJava VM上で動作するため、これまでのJava資産の多くを流用できる
    • Java JVMだけがターゲットでなく、JavaScript / Native コードの生成も可能である
  • Java VMで動作する他のプログラミング言語

基本

エントリポイント

  • main 関数がエントリポイントとなる

    fun main() {
        println("Hello world!")
    }
    
    // プログラム引数を取る場合
    fun main(args: Array<String>) {
        println(args.contentToString())
    }
    

標準出力

  • print / println 関数を利用する

    print("Hello ")
    print("world!")
    
    println("Hello world!")
    println(42)
    

構文

関数の定義

  • 2つのInt型の引数を持ち、Int型を戻り値とする関数

    fun sum(a: Int, b: Int): Int {
      return a + b
    }
    
    // 式本体と推論された戻り値の型
    fun sum(a: Int, b: Int) = a + b
    
  • 値を返さない関数

    fun printSum(a: Int, b: Int): Unit {
      print(a + b)
    }
    
    // Unitは省略可
    fun printSum(a: Int, b: Int) {
      print(a + b)
    }
    

変数の定義

  • 1度だけ代入できる(Immutable)ローカル変数

    val a: Int = 1
    val b = 1   // `Int`型が推論される
    val c: Int  // 初期値が与えられない場合、型指定が必要
    c = 1       // 明確な代入
    
  • 変更可能 (Mutable) な変数

    var x = 5 // `Int`型が推論される
    x += 1
    

null可能値を使用した、nullのチェック

  • null値を取り得る場合、参照は明示的にnullをチェックする必要あり

    // nullを返す可能性のある関数
    fun parseInt(str: String): Int? {
      // ...
    }
    
    // null可能値を返す関数を使用
    fun main(args: Array<String>) {
      if (args.size < 2) {
        print("Two integers expected")
        return
      }
    
      val x = parseInt(args[0])
      val y = parseInt(args[1])
    
      // `x`, `y`はnullが入っていることがあるので、`x * y`はエラーを引き起こす
      if (x != null && y != null) {
        // xとyは、nullチェックの後自動的に非null許容型へキャストされる
        print(x * y)
      }
    }
    
    // 別な例
    // null可能値を返す関数を使用
    fun main(args: Array<String>) {
      if (args.size < 2) {
        print("Two integers expected")
        return
      }
    
      val x = parseInt(args[0])
      val y = parseInt(args[1])
    
      if (x == null) {
        print("Wrong number format in '${args[0]}'")
        return
      }
      if (y == null) {
        print("Wrong number format in '${args[1]}'")
        return
      }
    
      // x and y are automatically cast to non-nullable after null check
      print(x * y)
    }
    

型チェックと自動キャストの使用(スマートキャスト)

  • is演算子で型チェックを行う (Javaのinstanceofに相当)

    • isで特定の型をチェックした後は、明示的なキャストが不要
    // 例1 : if内はStringとして参照
    fun getStringLength(obj: Any): Int? {
      if (obj is String) {
        // `obj` はこのブランチ内では自動的に`String`へキャストされる
        return obj.length
      }
    
      // `obj` は型チェックが行われたブランチ外では、まだ`Any`型である
      return null
    }
    
    // 例2 : ifの外側でStringとして参照
    fun getStringLength(obj: Any): Int? {
      if (obj !is String)
        return null
    
      // `obj` はこのブランチ内では自動的に`String`へキャストされる
      return obj.length
    }
    
    // 例3 : 遅延評価演算子 && とif内ではStringとして参照
    fun getStringLength(obj: Any): Int? {
      // `obj` は`&&`の右側では自動的に`String`へキャストされる
      if (obj is String && obj.length > 0)
        return obj.length
    
      return null
    }
    

範囲

  • in演算子で数値が範囲内にあるか判定(!inで範囲外を判定)

    // 範囲内の判定
    if (x in 1..y-1)
      print("OK")
    
    // 範囲外の判定
    if (x !in 0..array.lastIndex)
      print("Out")
    
    // forループでの使用
    for (x in 1..5)
      print(x)
    

基本型

数値

整数型
Type Size (bits) Min value Max value
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-231) 2,147,483,647 (231 - 1)
Long 64 -9,223,372,036,854,775,808 (-263) 9,223,372,036,854,775,807 (263 - 1)
浮動小数型
Type Size (bits) Significant bits Exponent bits Decimal digits
Float 32 24 8 6-7
Double 64 53 11 15-16

リテラル定数

  • 整数値のリテラル
    • 数値: 123
      • Long型の数を表すには大文字のLでタグ付けする: 123L
    • 16進数: 0x0F
    • 8進数: サポートしない
    • 2進数: 0b00001011
  • 浮動小数のリテラル
    • デフォルトではdouble型: 123.5, 123.5e10
    • float型を表すには f or F でタグ付けする: 123.5f

ボクシング

  • JVMプリミティブ型として数値が物理的に格納されている

    • ジェネリクス・null許容型(例: Int?)が関与すると、ボクシングされる
    val a: Int = 10000 // プリミティブな数値
    print(a === a) // 'true'を出力する
    val boxedA: Int? = a // ボクシングされた数値
    val anotherBoxedA: Int? = a  // ボクシングされた数値
    print(boxedA === anotherBoxedA) // !!! 'false'を出力する !!! : === 参照等価評価
    print(boxedA == anotherBoxedA) // 'true'を出力する : 構造等価評価 (.equalsに該当)
    

明示的な変換

  • 小さな型は大きな型のサブタイプではない

    • 明示的な変換なしで、Byte型の値をInt型へ代入することができないことを意味する
    • 異なる型の算術演算においては、算出される型は推論される
    val b: Byte = 1
    val i: Int = b // エラー
    val i: Int = b.toInt() // OK
    
    val l = 1L + 3 // Long + Int => Long
    
  • 全ての数値型は次の変換をサポートしている

    • toByte(): Byte, toShort(): Short, toInt(): Int , toLong(): Long, toFloat(): Float, toDouble(): Double, toChar(): Char

真偽値

  • Boolean型は真偽値を表し、true false の2つの値がある

文字

  • 文字は、Char型で表され、数字として直接扱うことはできない

    fun check(c: Char) {
      if (c == 1) { // ERROR: 非互換の型
        // ...
      }
    }
    
  • サポートされるエスケープシーケンス

    • \t, \b, \n, \r, \', \", \\, \$
    • Unicodeエスケープシーケンス: \uFF00

文字列

  • 文字列は、String型で表される
    • 文字列は不変(イミュータブル)
    • 文字列の要素は、インデックスの演算でアクセスできる:s[i]

文字列リテラル

  • 2つの種類の文字列リテラル

    • エスケープされた文字列を持ちうるエスケープ済み文字列

      val s = "Hello, world!\n"
      
    • 改行と任意の文字を含む生文字列

      val text = """
        for (c in "foo")
          print(c)
      """
      
      • 先頭の空白をtrimMargin()関数で削除する例

        • デフォルトでは|はマージンの接頭辞として使用されるが、trimMargin(">")のように、パラメータとして別の文字を渡すとそれを接頭辞として使用することができる
        val text = """
            |Tell me and I forget. 
            |Teach me and I remember. 
            |Involve me and I learn.
            |(Benjamin Franklin)
            """.trimMargin()
        

文字列テンプレート

  • 文字列はテンプレート式、すなわち、評価され、その結果が文字列と結合されるコードの断片を含むことができる

    val i = 10
    val s = "i = $i" // "i = 10"と評価される
    
    val s = "abc"
    val str = "$s.length is ${s.length}" // "abc.length is 3"と評価される
    
    // 生文字列内で $ を出力する例
    val price = """
    ${'$'}9.99
    """
    

パッケージ

パッケージ定義

  • ソースファイルの先頭でパッケージを記述する。

  • Javaとは異なり、ディレクトリとパッケージが一致する必要はない。

    package my.demo
    
    fun baz() {}
    
    class Goo {}
    
  • baz()の完全名はfoo.bar.bazであり、Gooの完全名は`foo.bar.Goo

    • パッケージが指定されない場合は、ファイルの内容は名前を持たないdefaultパッケージに属することになる

インポート

  • 単一の名前を指定してインポート

    import foo.Bar // Barは許可無しでアクセス可能になります
    
  • あるスコープ(パッケージ、クラス、オブジェクト等)内の全てのアクセス可能なコンテンツのインポート

    import foo.* // 'foo'内の全てがアクセス可能になります
    
  • 名前の衝突がある場合、asキーワードを使用して衝突するエンティティを局所的にリネーム

    import foo.Bar // Barはアクセス可能
    import bar.Bar as bBar // bBarは'bar.Bar'を意味する
    
  • Javaとは違って、Kotlinはimport static構文を持っていない

制御フロー

if式

  • Kotlinでは、ifは式であり、値を返す(ifを文ではなく式として使用する(例えば値を返したり変数に代入したりする)ならば、その式にはelse分岐が必要)

    • 従って、三項演算子は存在しない
    // 伝統的な文としての使用
    var max = a 
    if (a < b) 
      max = b 
    
    // else付き
    var max: Int
    if (a > b) 
      max = a 
    else 
      max = b 
    
    // 三項演算子のように式として利用
    val max = if (a > b) a else b
    
  • if の分岐はブロックにすることができ、最後の式がそのブロックの値となる

    val max = if (a > b) { 
        print("Choose a") 
        a 
      } 
      else { 
        print("Choose b") 
        b 
      }
    

when式

  • 文として利用した場合: elseは必須ではない

    when (x) {
      1 -> print("x == 1")
      2 -> print("x == 2")
      else -> {
        print("x is neither 1 nor 2")
      }
    }
    
    when (x) {
      0, 1 -> print("x == 0 or x == 1")
      else -> print("それ以外")
    }
    
  • 式として利用した場合: elseは必須

    fun valueToString(value: Int) =
        when(value) {
            1 -> "One"
            2 -> "Two"
            else -> "Unknown value (value = $value)"
        }
    
  • inまたは!inを使用すると、コレクションの 範囲 (range) が可能

    when (x) {
      in 1..10 -> print("xは範囲内")
      in validNumbers -> print("xは有効")
      !in 10..20 -> print("xは範囲外")
      else -> print("どれにも該当せず")
    }
    
  • 特定の型を検査するためにisまたは!isを利用

    • スマートキャストの機能により、その型のメソッドやプロパティに追加のチェック無しでアクセスできることに着目
    val hasPrefix = when(x) {
      is String -> x.startsWith("prefix") // x は String へキャストされている
      else -> false
    }
    

forループ

  • forループはイテレータによって提供されるものを繰り返し実行

    // ブロックなし
    for (item in collection)
      print(item)
    
    // ブロックあり
    for (item: Int in ints) {
      // ...
    }
    
  • ライブラリ関数のwithIndexを使用することで、要素のインデクスを取得する例

    for ((index, value) in array.withIndex()) {
        println("$indexの要素は$value")
    }
    

whileループ

while (x > 0) {
  x--
}

do {
  val y = retrieveData()
} while (y != null) // y はここで可視(visible)

returnとジャンプ

  • Kotlinの3つの構造的ジャンプ演算子
    • return: デフォルトでは最近のクロージャ(関数閉包)や匿名関数から抜け出す
    • break: 最も近い外側のループを終わらせる
    • continue: 最も近い外側のループを次のステップに進ませる

breakとcontinueのラベル

  • Kotlinにおける任意の式を labelでマークすることができる

    • ラベルは、 @ 記号に続く識別子の形式をもつ: abc@fooBar@が有効なラベル
    loop@ for (i in 1..100) {
      for (j in 1..100) {
        if (...)
          break@loop
      }
    }
    

ラベルに復帰

// 明示的なラベルを利用
fun foo() {
  ints.forEach lit@ {
    if (it == 0) return@lit
    print(it)
  }
}

// 暗黙のラベルを利用
fun foo() {
  ints.forEach {
    if (it == 0) return@forEach
    print(it)
  }
}

例外

例外クラス

  • Kotlinの例外は、Throwableのサブクラスとなる

    • Javaと異なりチェック例外はない
    // 例外のスロー
    throw MyException("Hi There!")
    
    // 例外のキャッチ
    try {
      // some code
    } catch (e: SomeException) {
      // handler
    } finally {
      // optional finally block
    }
    
    // try を式として利用する
    val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
    

クラスとオブジェクト

クラスと継承

クラス

  • Kotlinでのクラスは、classキーワードを使用して宣言

    class Invoice {
    }
    
    // 本体がない場合は {} を省略できる
    class Empty
    

コンストラクタ

  • Kotlin内のクラスは、 プライマリコンストラクタ と1つまたは複数の セカンダリコンストラクタ を持つことがでる

プライマリコンストラクタ

  • プライマリコンストラクタ: クラスのヘッダーの一部

    class Person constructor(firstName: String) {
    }
    
    // プライマリコンストラクタがアノテーション・可視装飾子を持たない場合 constructor キーワードを省略できる
    class Person(firstName: String) {
    }
    
  • プライマリコンストラクタは、コードを含めることができない

    • 初期化コードはinitキーワードを付与したブロック内に記述
    class Customer(name: String) {
        init {
            logger.info("Customer initialized with value ${name}")
        }
    }
    
  • プライマリコンストラクタの引数を使用して、プロパティを初期化する例

    class Customer(name: String) {
        val customerKey = name.toUpperCase() // プロパティの宣言と初期化
    }
    
  • プロパティの宣言と初期化をプライマリコンストラクタから行う例

    • プロパティはプライマリコンストラクタの中で可変値(ミュータブル) ( var ) または固定値(イミュータブル) ( val ) で宣言可能
    // コンストラクタの引数は、プロパティとして定義される
    class Person(val firstName: String, val lastName: String, var age: Int) {
      // ...
    }
    
  • プライマリコンストラクタがアノテーションや可視性修飾子を持つ場合は、 constructor キーワードが必要

    class Customer public @Inject constructor(name: String) { ... }
    

セカンダリコンストラクタ

  • セカンダリコンストラクタの宣言例

    class Person {
        constructor(parent: Person) {
            parent.children.add(this)
        }
    }
    
  • クラスがプライマリコンストラクタを持つなら、それぞれのセカンダリコンストラクタはプライマリコンストラクタへ委譲する必要あり

    • 同クラスの他コンストラクタへの委譲は this キーワードを利用する
    class Person(val name: String) {
        constructor(name: String, parent: Person) : this(name) {
            parent.children.add(this)
        }
    }
    
    • 非抽象クラスがコンストラクタの宣言をしない場合、引数なしのプライマリコンストラクタがpublicスコープで生成される

      • 引数なしのコンストラクタが必要でない場合、private constructorで宣言する
    class DontCreateMe private constructor () {
    }
    

インスタンスの生成

  • Kotlinはnewキーワードを持たない

    • コンストラクタの呼び出しは、関数の呼び出しと同様に行う
    val invoice = Invoice()
    
    val customer = Customer("Joe Smith")
    

クラスメンバ

継承

  • Kotlinの全てのクラスは共通の Any スーパークラスを持つ

    • Anyjava.lang.Object ではない
      • 特に注意すべきは、 equals()hashCode()toString() 以外のメンバを持たない
    class Example // Anyから暗黙の継承
    
  • クラスヘッダ内のコロンの後に型を書くと、明示的にスーパータイプを宣言できる

    • スーパータイプのクラスがプライマリコンストラクタを持つなら、継承したクラスのプライマリコンストラクタの値を利用して、初期化しなければならない
    open class Base(p: Int)
    
    class Derived(p: Int) : Base(p)
    
    • スーパータイプがプライマリコンストラクタを持たないならば、セカンダリコンストラクタでsuperキーワードを利用して初期化する
    class MyView : View {
        constructor(ctx: Context) : super(ctx) {
        }
    
        constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
        }
    }
    
  • クラスの open アノテーションは、Javaの final と反対

    • デフォルトでは、Kotlinのすべてのクラスは Effective Java のアイテム17( 継承またはそれの禁止のためのデザインとドキュメント )に合致する finalとなる

オーバーライド

  • Kotlinは明示的であり、Javaとは異なりオーバーライドできるメンバにはopenキーワードを付与する

    • サブクラスでは、overrideキーワードを利用して、スーパークラスの関数をオーバーライドする
    • openキーワードのないクラスでは、openなメンバは禁止されている
    open class Base {
      open fun v() {} // オーバーライド可能
      fun nv() {} // オーバーライド不可
    }
    
    class Derived() : Base() {
      override fun v() {} // Base .v() をオーバーライド、 open を引き継ぐ
    }
    
    open class AnotherDerived() : Base() {
      final override fun v() {} // Base .v() をオーバーライド、 これ以上のオーバーライドは不可
    }
    
  • プロパティのオーバライドもメソッドのオーバライドと同様

    open class Foo {
        open val x: Int get { ... }
    }
    
    class Bar1(override val x: Int) : Foo() {
    }
    

抽象クラス

  • クラスとそのメンバは abstractキーワードを使用して抽象クラス・抽象メンバとして宣言できる

    • 抽象クラスや抽象関数にopenキーワードを付ける必要はない
    open class Base {
      open fun f() {}
    }
    
    abstract class Derived : Base() {
      override abstract fun f()
    }
    

コンパニオンオブジェクト

  • Kotlinでは、JavaやC#とは異なり、クラスはstaticメソッドを持たない
    • ほとんどの場合、代替として、パッケージレベルの関数を使用することが推奨されている
    • もしクラスインスタンスを持たずに呼べるがクラス内部(例えばファクトリメソッド)へのアクセスが要る関数を書く必要があれば、そのクラスの中で オブジェクト宣言 のメンバとして書くことができる

プロパティとフィールド

プロパティの宣言

  • Kotlinのクラスは、プロパティを持てる

    • var キーワード: ミュータブル(可変)
    • val キーワード: イミュータブル(読み取り専用)
    public class Address { 
      public var name: String = ...
      public var street: String = ...
      public var city: String = ...
      public var state: String? = ...
      public var zip: String = ...
    }
    
  • プロパティの参照

    • Javaのフィールド参照と同様
    fun copyAddress(address: Address): Address {
      val result = Address() // 'new' キーワードは Kotlin にありません
      result.name = address.name // アクセサが呼ばれる
      result.street = address.street
      // ...
      return result
    }
    

ゲッターとセッター

  • プロパティを宣言するための構文

    • イニシャライザ、ゲッターとセッターは必須ではない
    // ミュータブル (var) の場合
    var allByDefault: Int? // エラー:明示的なイニシャライザが必要、デフォルトのゲッターとセッターは暗黙
    var initialized = 1 // これは Int 型を持ち、ゲッターとセッターも持つ
    
    // イミュータブル (val) の場合 : setter が生成されない
    val simple: Int? // Int 型を持ち、デフォルトゲッターを持つ。コンストラクタ内で初期化が必要
    val inferredType = 1 // Int 型を持ち、デフォルトゲッターを持つ
    
  • カスタムアクセサの定義

    // カスタム getter
    val isEmpty: Boolean
      get() = this.size == 0
    
    // カスタム setter
    var stringRepresentation: String
      get() = this.toString()
      set(value) {
        setDataFromString(value) // 文字列をパースして他のプロパティへ値を代入する
      }
    
  • アクセサの可視性を変更したり、アノテーションを付ける必要があるが、デフォルトの実装を変更する必要がない場合

    • 本体を定義せずにアクセサを定義することがでる
    var setterVisibility: String = "abc"
      private set // セッターはプライベートでデフォルトの実装を持つ
    
    var setterWithAnnotation: Any? = null
      @Inject set // セッターに Inject でアノテーションを付ける
    

バッキングフィールド (Backing Fields)

  • Kotlinのクラスは、フィールドを持てない

    • カスタムアクセサを使用するときにバッキングフィールドが必要になることがある
      • この目的のために、Kotlinは自動バッキングフィールドを提供する
        • field 識別子を使用してアクセスすることができる
    var counter = 0 // イニシャライザの value はバッキングフィールドへ直に書き込まれる
      set(value) {
        if (value >= 0)
          field = value // バッキングフィールドへ値を設定する
      }
    

コンパイル時定数

  • 値がコンパイル時にわかるプロパティは、 const キーワードを使用して、 コンパイル時定数 (compile time constants) としてマークすることができる

    • 次の要件を満たす場合に利用できる
      • トップレベルまたは object のメンバ
      • String型の値またはプリミティブ型で初期化される
      • カスタムゲッターが無い
    const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
    
    @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
    

遅延初期化プロパティ

  • 通常、非null型として宣言されたプロパティは、コンストラクタ内で初期化が必要

    • lateinit 修飾子でプロパティをマークすることで、コンストラクタで初期化ができないケースをカバー
      • カスタムゲッターやカスタムセッターを持たない var プロパティでのみ使用でき、プロパティの型が非nullかつ、プリミティブ型であってはならない
      • lateinit プロパティが初期化される前にアクセスした場合、アクセスされたプロパティと、それが初期化されていないことを特定するための例外がスローされる
    public class MyTest {
        lateinit var subject: TestSubject
    
        @SetUp fun setup() {
            subject = TestSubject()
        }
    
        @Test fun test() {
            subject.method()  // 参照先を直に見に行く(dereference directly)
        }
    }
    

継承

インターフェース

  • interface キーワードを使用して定義

    interface MyInterface {
        fun bar()
        fun foo() {
          // 本体は任意
        }
    }
    

インタフェースの実装

  • クラスやオブジェクトは、複数のインターフェイスを実装すできる

    class Child : MyInterface {
       override fun bar() {
          // 本体
       }
    }
    

インターフェイス内のプロパティ

  • プロパティの宣言が可能

    interface MyInterface {
        val property: Int // abstract
    
        val propertyWithImplementation: String
            get() = "foo"
    
        fun foo() {
            print(property)
        }
    }
    
    class Child : MyInterface {
        override val property: Int = 29
    }
    

オーバーライドの競合解決

interface A {
  fun foo() { print("A") }
  fun bar()
}

interface B {
  fun foo() { print("B") }
  fun bar() { print("bar") }
}

class C : A {
  override fun bar() { print("bar") }
}

class D : A, B {
  override fun foo() {
    super<A>.foo()
    super<B>.foo()
  }
}

可視性修飾子

  • 4つの可視性修飾子 (visibility modifiers)
    • private
    • protected
    • internal
    • public : デフォルト

パッケージ

  • 関数、プロパティやクラス、オブジェクトやインターフェースは、「トップレベル」、つまり、パッケージ内部で直接宣言することができる

    • 可視性修飾子を何も指定しない: どこからでも参照可、デフォルト public
    • private : その宣言を含むファイルの中でのみ参照可
    • internal : 同一モジュール内から参照可
    • protected : トップレベルの宣言では使用不可
    // ファイル名: example.kt
    package foo
    
    private fun foo() {} // example.kt の中で見える
    
    public var bar: Int = 5 // プロパティはどこでも見える
        private set         // セッターは example.kt の中でのみ見える
    
    internal val baz = 6    // 同じモジュール内でのみ見える
    

クラスとインタフェース

  • クラス内のメンバーで指定した場合

    • private : そのクラス内のみ参照可
      • 注意 Javaのユーザーへ:Kotlinでは、外部クラスはその内部クラスのprivate メンバを参照不可
    • protected : private と同じ + サブクラス内のみ参照可
      • 注意 Javaのユーザーへ:Javaでは同一パッケージ内からでもprotected のメンバ参照できるが、Kotlinでは不可
    • internal : 同一モジュール内から参照可
    • public – どこからでも参照可
    open class Outer {
        private val a = 1
        protected open val b = 2
        internal val c = 3
        val d = 4  // デフォルトで public
    
        protected class Nested {
            public val e: Int = 5
        }
    }
    
    class Subclass : Outer() {
        // a は見えない
        // b, c, d は見える
        // Nested と e は見える
    
        override val b = 5   // 'b' は protected
    }
    
    class Unrelated(o: Outer) {
        // o.a, o.b は見えない
        // o.c and o.d は見える(同じモジュール)
        // Outer.Nested, Nested::e は見えない
    }
    

モジュール

  • モジュールとは
    • Kotlin のファイルが一体としてコンパイルされるまとまりのこと
        • IntelliJ IDEAモジュール
        • MavenやGradleのプロジェクト
  • Javaでは、外部のコードが同じパッケージ名でクラスを定義すれば元のコードのパッケージプライベートな宣言を参照できるが、kotlinでは不可
    • 実装のカプセル化を防ぐための機能

拡張 (extension)

  • クラスを新しい機能で拡張する機能
    • 拡張関数拡張プロパティ をサポート

拡張関数

  • 拡張関数を宣言するには レシーバタイプ (receiver type) を関数名の前に付ける

    • 拡張関数内での this キーワードは、レシーバオブジェクト(ドットの前に渡されたもの)に対応する
    // MutableListにswap 関数を追加する例
    fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
      val tmp = this[index1] // 'this' はリストに対応する
      this[index1] = this[index2]
      this[index2] = tmp
    }
    

null許容レシーバー

  • 拡張はnull許容なレシーバの型でも定義できる

    • 対象の値がnullの場合でも、オブジェクト変数で呼び出すことができ、かつその本体内で this == null をチェックすることが可能
    fun Any?.toString(): String {
        if (this == null) return "null"
        // nullチェックの後だと、 'this' は非null型に自動キャストされるので、
        // 下記の toString() は Any クラスのメンバであると解決される
        return toString()
    }
    

データクラス

  • データを保持するためだけのクラス (DTO, VO)

    • プライマリコンストラクタで宣言されたすべてのプロパティから、コンパイラは自動的に次のメンバを推論
      • equals() / hashCode() のペア
      • "User(name=John, age=42)" 形式の toString()
      • 宣言した順番でプロパティに対応する componentN() 関数
      • copy() 関数
    • 制約
      • プライマリコンストラクタは、少なくとも1つのパラメータを持ち、 val または var としてマークする
      • データクラスは、abstract, open, sealed inner にできない
    data class User(val name: String, val age: Int)
    
    // デフォルト値を指定することで、パラメータなしのコンストラクタを定義できる
    data class User(val name: String = "", val age: Int = 0)
    

コピー

  • 自動的に生成されるcopy()関数は、対象のオブジェクトのクローンを生成する

    val jack = User(name = "Jack", age = 1)
    val olderJack = jack.copy(age = 2)
    

データクラスと分解宣言 (Destructuring Declarations)

  • 分解宣言で特定のプロパティ値を変数に展開ができる

    val jane = User("Jane", 35) 
    val (name, age) = jane // Destructuring
    println("$name, $age years of age") // "Jane, 35 years of age" を出力する
    

標準データクラス

  • 標準ライブラリは、 PairTriple を提供(tupleに相当)

ジェネリクス

  • Javaと同様に、型パラメータを持てる

    class Box<T>(t: T) {
      var value = t
    }
    
    // インスタンスを生成する
    val box: Box<Int> = Box<Int>(1)
    
    // 推論により型引数を省略できる
    val box = Box(1)
    

分散

  • Javaのジェネリクスにあるワイルドカード(JavaのジェネリックのFAQを参照)がない
    • Javaでのワイルドカード例: Collection<? extends Object> , List<? super String>
      • extends : 型共変 - Producerで利用する -> Kotlin out修飾子
      • super : 反変性 - Consumerで利用する -> Kotlin in修飾子
  • Tips : Wikipedia 共変性と反変性 (計算機科学)より
    • 共変 (covariant): 広い型(例:double)から狭い型(例:float)へ変換すること
    • 反変 (contravariant) : 狭い型(例:float)から広い型(例:double)へ変換すること
    • 不変 (invariant): 型を変換できないこと

宣言箇所分散

分散アノテーション
  • out / in 修飾子をしない型パラメータは不変となる

    open class Present(private val price: Int)
    class Sweets(price: Int, private val flavor: String) : Present(price)
    class HandCream(price: Int) : Present(price)
    
    // T はへ不変
    class Box<T : Present>(private val contents: T) {
        fun take(): T = contents
        fun <R> take(predicate: (T) -> R): R = predicate(contents)
    }
    
    fun main() {
        var candidate1: Box<Present> = Box(Present(3000))
        var candidate2: Box<Sweets> = Box(Sweets(2000, "ビターチョコ"))
    
        candidate1 = candidate2 // コンパイルエラー : Type mismatch
    }
    
    
out 修飾子
  • 「読み取りはするが、変更はしない」状態にすることで、広い型から狭い型への変換が可能

    // T は out 修飾子により共変
    class Box<out T : Present>(private val contents: T) {
        fun take(): T = contents
        fun <R> take(predicate: (T) -> R): R = predicate(contents)
    }
    
    fun main() {
        var candidate1: Box<Present> = Box(Present(3000))
        var candidate2: Box<Sweets> = Box(Sweets(2000, "ビターチョコ"))
    
        candidate1 = candidate2 // OK
    }
    
  • 例) Kotlinの Listimmutable であるため、 out修飾子が利用されている

    public interface List<out E> : Collection<E>
    
in 修飾子
  • 「変更はするが、読み取りはしない」状態にすることで、狭い型から広い型への変換が可能

    // T は in 修飾子により反変
    class Box<in T : Present>(private var contents: T) {
        //他のTを引数に取り、プロパティのTを書き換える「だけの」メソッド (内部のT(contentsプロパティ)を参照してはいけない)
        fun changeContents(other: T) {
            contents = other
        }
    
        // 値を参照するような処理はできない
        //fun <R> take(predicate: (T) -> R): R = predicate(contents)
    }
    
    fun main() {
        val candidate1 = Box(Present(3000))
        var candidate2 = Box(Sweets(2000, "ビターチョコ"))
    
        // 広い型を狭い型へ代入する = 狭い型を広い型に変換することが可能
        candidate2 = candidate1
    
        candidate2.changeContents(Sweets(5000, "抹茶チョコ"))
    }
    
タイププロジェクション(型投影)
  • 例として、配列を表す Array は読み書きができるため 不変 となる

    // ある配列から別の配列へ、アイテムをコピーする関数
    fun copy(from: Array<Any>, to: Array<Any>) {
      assert(from.size == to.size)
      for (i in from.indices)
      to[i] = from[i]
    }
    
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val any = Array<Any>(3) {}
    copy(ints, any) // エラー: (Array<Any>, Array<Any>) が期待されている
    
  • out 修飾子を利用して、copy 関数が from に変更を禁止することを保証する

    • Javaの Array<? extends Object> に対応
    fun copy(from: Array<out Any>, to: Array<Any>) {
    ...
    }
    
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val any = Array<Any>(3) {}
    copy(ints, any) // OK
    
  • in修飾子を利用して、与えられたパラメータの読み込みを禁止することを保証する

    • Javaの Array<? super String> に対応
    // dest の型パラメータに in を指定してるため dest は 引数に型パラメータを使うメソッドしか使えない
    fun fill(dest: Array<in String>, value: String) {
      // ...
    }
    
スタープロジェクション (star-projections)
  • Java において Some<?> の様に、型パラメータを指定しない場合、Kotlinでは Some<*> と記述し、スタープロジェクションと呼ばれる。

    • Some<out Any?>と同じ意味を持つ
    val ints: List<Int> = listOf(1, 2, 3)
    val otherInts: List<*> = ints // スタープロジェクション
    
    // otherIntList は何の型を内部に格納しているか不明なので、 Any? としてしか取り出せない
    val a: Any? = otherInts[0] // OK
    val n: Number = otherInts[0] // エラー
    val i: Int = otherInts[0]) // エラー
    

ネストクラス

クラスのネスト

  • クラス内にクラスを定義できる

    class Outer {
      private val bar: Int = 1
      class Nested {
        fun foo() = 2
      }
    }
    
    val demo = Outer.Nested().foo() // == 2
    

内部クラス

  • innerを付与することで、外部クラスのメンバーを参照できる

    class Outer {
      private val bar: Int = 1
      inner class Inner {
        fun foo() = bar
      }
    }
    
    val demo = Outer().Inner().foo() // == 1
    

無名内部クラス

  • オブジェクト式を利用して、無名内部クラスのインスタンスを生成する

    window.addMouseListener(object: MouseAdapter() {
      override fun mouseClicked(e: MouseEvent) {
        // ...
      }
                                                          
      override fun mouseEntered(e: MouseEvent) {
        // ...
      }
    })
    
  • 生成するオブジェクトがFunction Interface(インターフェースに抽象メソッドが1つだけ宣言されている)場合は、ラムダ式を使用してオブジェクトを作成できる

    val listener = ActionListener { println("clicked") }
    

列挙型クラス (Enum Classes)

  • 基本的な列挙型の例

    enum class Direction {
      NORTH, SOUTH, WEST, EAST
    }
    

メンバの初期化

  • メンバを持つ列挙型の例

    enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
    }
    

無名クラス

  • 独自の無名クラスを宣言する例

    enum class ProtocolState {
      WAITING {
        override fun signal() = TALKING
      },
    
      TALKING {
        override fun signal() = WAITING
      };
    
      abstract fun signal(): ProtocolState
    }
    

列挙型に用意されたメソッド・プロパティ

  • Javaのenumと同様のメソッド・プロパティが提供される

    // emum class EnumClass を定義した場合の例
    
    // 指定された名前が、クラスで定義されている列挙型定数のいずれとも一致しない場合、valueOf() メソッドは IllegalArgumentException をスロー
    EnumClass.valueOf(value: String): EnumClass
    EnumClass.values(): Array<EnumClass>
    
  • Javaと同様に、すべての列挙型定数は、列挙型クラス宣言でその名前と順序を取得するためのプロパティが提供される

    • 定義された順に基づいたComparable インタフェースも提供される
    val name: String
    val ordinal: Int
    

オブジェクト

オブジェクト宣言

  • Kotlinにおけるオブジェクトはシングルトンである

    • 初回アクセス時に 遅延して 初期化される
    // オブジェクトの宣言
    object DataProviderManager {
      fun registerDataProvider(provider: DataProvider) {
        // ...
      }
    
      val allDataProviders: Collection<DataProvider>
        get() = // ...
    }
    
    // オブジェクトの参照
    DataProviderManager.registerDataProvider(...)
    
    // スーパータイプを持つ宣言の例
    object DefaultListener : MouseAdapter() {
      override fun mouseClicked(e: MouseEvent) {
        // ...
      }
    
      override fun mouseEntered(e: MouseEvent) {
        // ...
      }
    }
    

コンパニオンオブジェクト (Companion Objects)

  • クラス内におけるオブジェクトの宣言はcompanionキーワードを使用する

    • Javaにおけるstaticに相当する利用方法となる
    • 対応するクラスが読み込まれたときに初期化される
    class MyClass {
      companion object Factory {
        fun create(): MyClass = MyClass()
      }
    }
    
    // コンパニオンオブジェクトのメンバーを呼び出す例
    val instance = MyClass.create()
    

オブジェクト式

  • あるクラスをわずかに修正しただけのオブジェクトを、それのための新しいサブクラスを明示的に宣言せずに、作成する必要がある時、Javaでは 無名内部クラス で対応する

    • Kotlinではobjectキーワードを利用した無名クラスのオブジェクトの生成で対応する
    // MouseAdapterのオブジェクトを生成する例
    window.addMouseListener(object : MouseAdapter() {
      override fun mouseClicked(e: MouseEvent) {
        // ...
      }
    
      override fun mouseEntered(e: MouseEvent) {
        // ...
      }
    })
    
    // 複数のスーパータイプを持つ例
    open class A(x: Int) {
      public open val y: Int = x
    }
    
    interface B {...}
    
    val ab: A = object : A(1), B {
      override val y = 15
    }
    
    // 一時的なオブジェクトのみが必要な場合の例
    val adHoc = object {
      var x: Int = 0
      var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
    

委譲 (Delegation)

クラスの委譲

  • 実装継承はカプセル化を破るためDelegationパターンが代替として推奨される(もちろんケースバイケースではある)

    interface Base {
      fun print()
    }
    
    class BaseImpl(val x: Int) : Base {
      override fun print() { print(x) }
    }
    
    // スーパータイプのリスト中の by 節は、 b が Derived のオブジェクトに内部的に格納されることを示し、コンパイラは b に取り次ぐ Base のすべてのメソッドを生成
    class Derived(b: Base) : Base by b
    
    fun main(args: Array<String>) {
      val b = BaseImpl(10)
      Derived(b).print() // 出力:10
    }
    

委譲プロパティ (Delegated Properties)

  • 委譲を行うことで、次の一般的なユースケースに対応できる

    • 遅延プロパティ (lazy properties) :アクセス時に算出する
    • オブザーバブルプロパティ (observable properties) :プロパティが変更された時に通知する
    class Example {
      var p: String by Delegate()
    }
    
    class Delegate {
      operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
      }
     
      operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
      }
    }
    
    val e = Example()
    println(e.p) // -> Example@33a17727, thank you for delegating ‘p’ to me!
    

関数とラムダ

関数

関数の宣言と呼び出し

  • funキーワードで宣言する

    // 宣言
    fun double(x: Int): Int {
    }
    
    // 呼び出し
    val result = double(2)
    

中置記法

  • infixキーワードを付与することで中置記法で関数を実行できる

    • 中置記法が使えるケース
    // Intにエクステンションを定義
    infix fun Int.shl(x: Int): Int {
    ...
    }
    
    // 中置記法で実行
    1 shl 2
    
    // これは次と同じ
    1.shl(2)
    

引数のデフォルト値

  • 関数パラメータは、対応する引数が省略されている際に使用されるデフォルト値を持てる

    fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size()) {
    ...
    }
    

名前付きパラメータ

  • 関数の呼び出し時に、関数のパラメータに名前を付けて呼ぶことができる

    • Javaバイトコードは関数パラメータの名を保存しないため、Java関数を呼び出すときに名前付きパラメータ構文は使用できない
    fun reformat(str: String,
                 normalizeCase: Boolean = true,
                 upperCaseFirstLetter: Boolean = true,
                 divideByCamelHumps: Boolean = false,
                 wordSeparator: Char = ' ') {
    ...
    }
    
    // デフォルト引数を利用して関数を呼び出す
    reformat(str)
    
    // 全ての引数を指定して関数を呼び出す
    reformat(str, true, true, false, '_')
    
    // 名前付き引数で関数を呼び出す
    reformat(str,
        normalizeCase = true,
        upperCaseFirstLetter = true,
        divideByCamelHumps = false,
        wordSeparator = '_'
      )
    
    // 名前付き引数を利用して一部の引数を指定して呼び出す
    reformat(str, wordSeparator = '_')
    

Unit

  • 関数が任意の有用な値を返さない場合、その戻り値の型は Unitとなる(Javaのvoid/Voidに相当)

    fun printHello(name: String?): Unit {
        if (name != null)
            println("Hello ${name}")
        else
            println("Hi there!")
        // `return Unit` または `return` は必須ではない
    }
    
    // Unitは省略できる(上記の関数宣言と同一となる)
    fun printHello(name: String?) {
        ...
    }
    

単一式関数

  • 関数の内部が単一の式である場合(つまり1行だけの場合)、中括弧を省略できる

    fun double(x: Int): Int = x * 2
    
    // コンパイラが型推論できる場合は、戻り値の型宣言を省略できる
    fun double(x: Int) = x * 2
    

可変長パラメータ

  • 関数のパラメータ(最後のひとつ)は、 vararg 修飾子で可変長パラメータにできる

    // ts 変数は Array<out T> 型
    fun <T> asList(vararg ts: T): List<T> {
      val result = ArrayList<T>()
      for (t in ts) // ts は配列
        result.add(t)
      return result
    }
    
    // 可変長パラメータを指定して関数を呼び出す
    val list = asList(1, 2, 3)
    

スコープ

ローカル関数

  • ある関数内に別の関数を定義できる

    • ローカル関数は、外側の関数(すなわちクロージャ)のローカル変数にアクセス可能
    fun dfs(graph: Graph) {
      val visited = HashSet<Vertex>()
    
      fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
          dfs(v)
      }
    
      dfs(graph.vertices[0])
    }
    

メンバ関数

  • クラスやオブジェクトの内部で定義される関数(いわゆるメソッド)

ジェネリック関数

  • 関数名の前に山括弧(<>のこと)を使用して、ジェネリックパラメータを持てる

    fun <T> singletonList(item: T): List<T> {
      // ...
    }
    

末尾再帰関数

  • 末尾再帰と呼ばれる関数型プログラミングのスタイルをサポート、通常の再帰と異なりスタックオバーフローのリスクがない

  • 関数が tailrec 修飾子でマークされており、必要な形式を満たしている場合、コンパイラは高速かつ効率的なループベースのバージョンコードを生成する

    • 必要な形式 : 関数は実行する最後の操作として自身を呼び出すこと
    // 末尾再帰の関数
    tailrec fun findFixPoint(x: Double = 1.0): Double
            = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
    
    // 生成されるループベースのコード
    private fun findFixPoint(): Double {
        var x = 1.0
        while (true) {
            val y = Math.cos(x)
            if (x == y) return y
            x = y
        }
    }
    

高階関数とラムダ

高階関数

  • 高階関数はパラメータとして関数を取るか、関数を返す関数のことを指す

ラムダ

  • 概要

    • ラムダ式は、中括弧で囲まれる
    • パラメータ(もしあれば)は、 -> の前で宣言される(パラメータの型を省略可能)
    • 本体が -> に続く(存在する場合)
    // body は関数型: () -> Tを持つ(パラメータを取らず、型 T の値を返す関数)
    fun <T> lock(lock: Lock, body: () -> T): T {
      lock.lock()
      try {
        return body()
      }
      finally {
        lock.unlock()
      }
    }
    
    // lock関数を呼び出す例
    fun toBeSynchronized() = sharedResource.operation()
    val result = lock(lock, ::toBeSynchronized) // ::は関数の参照を示す
    
    // ラムダ式を利用してlock関数を呼び出す例
    val result = lock(lock, { sharedResource.operation() })
    
    // Kotlinでは、関数の最後のパラメータが関数である場合、そのパラメータは括弧の外に指定することができる
    val result = lock (lock) {
      sharedResource.operation()
    }
    
    val sum = { x: Int, y: Int -> x + y }
    
    // sum変数に明示的に型を記載した場合
    val sum: (Int, Int) -> Int = { x, y -> x + y }
    

it : 単一パラメータの暗黙の名前

  • 関数リテラルがパラメータを1つだけ持つ場合、その宣言を( -> と一緒に)省略でき、暗黙のパラメータ名は it となる

    // 高階関数 map
    fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
      val result = arrayListOf<R>()
      for (item in this)
        result.add(transform(item))
      return result
    }
    
    // ラムダ式を利用して呼び出す
    ints.map({ it -> it * 2 })
    ints.map { it -> it * 2 }
    ints.map { it * 2 }
    

無名関数

  • ラムダ式の構文から一つ欠落しているのは、関数の戻り値の型を指定する機能

    • ほとんどの場合は、戻り型を自動的に推論することができるので不要だが、明示的な型指定が必要な場合は無名関数を利用する
    fun(x: Int, y: Int): Int = x + y
    
    // ブロックを使用した場合
    fun(x: Int, y: Int): Int {
      return x + y
    }
    
    // 無名関数を別の関数のパラメータとして渡す
    ints.filter(fun(item) = item > 0)
    

ラムダ式と無名関数の違い

  • ラベルなしreturnの動作が異なる

    ラムダ式 無名関数
    return は囲んでいる関数から返される 無名関数の内部 return は無名関数自体から返される

クロージャ

  • 外側のスコープで宣言された変数にアクセスすることができ、Javaとは異なりクロージャに取り込まれた変数を変更することができる(Javaではfinal宣言をしなければならない)

    var sum = 0
    ints.filter { it > 0 }.forEach {
      sum += it
    }
    print(sum)
    

分解宣言 (Destructuring Declarations)

  • オブジェクトの各プロパティが保持している値を、別々の変数に一度に代入することができる

    data class Person(
      var name: String,
      var age: Int
    )
    
    val p = Person(name = "yuki.terai", age = 17)
    val (name, age) = p
    // name=>"yuki.terai"
    
    
    // 宣言の順番通りに変数に入れていくだけなので、名前の一致はk関係ないため注意
    data class Person(
      var age: Int,
      var name: String
    )
    
    val p = Person(name = "yuki.terai", age = 17)
    val (name, age) = p
    // name=>17
    

コレクション

  • Kotlinは可変コレクションと不変コレクション(ListSetMapなど)を区別する

    • KotlinのList<out T>sizegetなどの参照のみ定義される
    • リストを変更するメソッドは、MutableList<T>インターフェースによって追加されている
    val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
    val readOnlyView: List<Int> = numbers
    println(numbers)        // prints "[1, 2, 3]"
    numbers.add(4)
    println(readOnlyView)   // prints "[1, 2, 3, 4]"
    readOnlyView.clear()    // -> does not compile
    
    val strings = hashSetOf("a", "b", "c", "c")
    assert(strings.size == 3)
    

等価

  • Kotlinには2つの等価性がある

    参照の等価性 (Javaの==) 構造的な等価性 (Javaのequals)
    === 演算子(および!== ==演算子(および!=

null安全

Nullable と Non-Null

  • Kotlinでは、nullを保持できる参照と、保持できない参照を区別する

    // Non-Null
    var a: String = "abc"
    a = null // compilation error
    a.length // ok
    
    // Nullable
    var b: String? = "abc"
    b = null // ok
    b.length // compilation error: variable 'b' can be null
    

Nullableに対する参照方法

nullをチェック

  • Nullable な型に対し、nullのチェックを行うことで、メンバへのアクセスが可能になる

    var b: String? = "abc"
    
    val l = if (b != null) b.length else -1 // ok
    
    if (b != null && b.length > 0)
      print("String of length ${b.length}") // このブロックは、null でないことが明白であることがコンパイラは知っている
    else
      print("Empty string")
    

?.演算子

  • Nullable のオブジェクトのメンバにアクセスする際に?.を利用することで、安全に呼び出しが行える

    var b: String? = "abc"
    
    val l: Int? = b?.length // b が null の場合は l に null が入る
    
    // チェーンすることも可能
    bob?.department?.head?.name // いずれかのプロパティが null であれば null が返る
    

エルビス演算子

  • null でない場合はその値を返し、そうでない場合はnull以外の値を使用する

    // if 利用した場合
    val l: Int = if (b != null) b.length else -1
    
    // エルビス演算子を利用した場合
    val l = b?.length ?: -1
    

!!.演算子

  • nullの時にNullPointerExceptionを発生させる(NPEの愛好家向け、利用はよく考えること)

    var b: String? = "abc"
    b = null
    val l = b!!.length() // NullPointerException が発生する
    

アノテーション

宣言

  • classの前にannotationを付与する

    • アノテーションの追加属性は、アノテーションクラスにメタアノテーションを付けることで指定できる
      • @Target : アノテーションを付けることができる要素(クラス、関数、プロパティ、式など)を指定する
      • @Retention : アノテーションがコンパイルされたクラスファイルに保存されるかどうか、および実行時にリフレクションを通じて表示されるかどうかを指定する(デフォルトでは両方ともtrue
      • @Repeatable : 1つの要素に同じ注釈を複数回使用できる
      • @MustBeDocumented : アノテーションがパブリックAPIの一部であり、生成されたAPIドキュメントに示されているクラスまたはメソッドのシグネチャに含まれている必要があることを指定する
    @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
            AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
    @Retention(AnnotationRetention.SOURCE)
    @MustBeDocumented
    public annotation class Fancy
    
    // アノテーションの使用例
    @Fancy class Foo {
      @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
      }
    }
    

コンストラクタ

  • アノテーションには、パラメーターを受け取るコンストラクターを指定できる

    • 許可されるパラメータタイプは次のとおりです。
      • Javaプリミティブ型(Int、Longなど)に対応する型
      • 文字列
      • クラス(Foo::class);
      • enum
      • その他のアノテーション
      • 上記のタイプの配列
    annotation class Special(val why: String)
    
    @Special("example") class Foo {}
    

アノテーションを適用するJavaの要素

  • プロパティまたはプライマリコンストラクタパラメータに注釈を付ける場合、Kotlinから生成されたJavaバイトコード内の注釈の可能な場所は複数になる。そのため、アノテーションを適用する箇所を指定することができる

    • file
    • property (このターゲットの注釈はJavaには表示されません)
    • field
    • get (プロパティゲッター)
    • set (プロパティセッター)
    • receiver (拡張関数またはプロパティのレシーバーパラメーター)
    • param (コンストラクターパラメーター)
    • setparam (プロパティセッターパラメーター)
    • delegate (委任されたプロパティの委任インスタンスを格納するフィールド)
    class Example(@field:Ann val foo,    // annotate Java field
                  @get:Ann val bar,      // annotate Java getter
                  @param:Ann val quux)   // annotate Java constructor parameter
    

リフレクション

  • ::構文を利用する

    class MyClass
    
    // Class References
    val kClass: KClass<MyClass> = MyClass::class
    val javaClass: Class<MyClass> = MyClass::class.java
    
    // Function References
    fun isOdd(x: Int) = x % 2 != 0
    val refFun1: (Int) -> Boolean = ::isOdd
    val refFun2: Function1<Int, Boolean> = ::isOdd
    
    // Property References
    val str = "a"
    val refProperty: KProperty<Int> = str::length
    val refJavaGetter: Method? = str::length.javaGetter
    val refJavaField: Field? = str::length.javaField
    
    // Constructor References
    val refConstructor: Function<MyClass> = ::MyClass
    

オペレータオーバーローディング

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?