Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ohtsuchi/d93c0411e1bacb387a02366f5a8d4cf8 to your computer and use it in GitHub Desktop.
Save ohtsuchi/d93c0411e1bacb387a02366f5a8d4cf8 to your computer and use it in GitHub Desktop.
Kotlinスタートブック読書勉強会 #5 第8章 補足

Kotlinスタートブック読書勉強会 #5 第8章 補足


reference や他のサイトなどに記述があって、赤べこ本では割愛されている(または ver1.1 以降の)内容の引用 が中心

または 赤べこ本 第9章 以降 に掲載 されていて, 8章 のタイミングでついでに習った方が良さそうな内容


3. Property

Classes and Objects - Properties and Fields - Getters and Setters 参照

full syntax

The full syntax for declaring a property is

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
  • kotlin で "initializer" だけで web検索すると以下の 2つ が出てくるので 区別注意
    • property initializer (↑)
    • initializer blocks (init {...})
      • -> 赤べこ本 第8章-5 (p123) 参照

list 8.7 custom getter の 省略記法

Since Kotlin 1.1, you can omit the property type if it can be inferred from the getter:

get() から 型推論 可能(Since 1.1)

val isEmpty get() = this.size == 0  // has type Boolean
    // list 8.7 `: Int` 省略可
    val nameLength2 // : Int
        get() = this.name.length

change the visibility of an accessor or to annotate it

可視性を変更 or Annotation 指定

// 外部(別のFile or 別のClass)から set が不可能な `var`
var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject
  • private
    • -> 赤べこ本 第9章-6 可視性 (p138, p139) 参照
class A {
    var setterVisibility: String = "abc"
        private set

    var other: Int = 0
        set(v) {
            // 自class内からはset可能
            setterVisibility = (v * 2).toString()
        }
}

fun main(args: Array<String>) {
    var a = A()
    // 外部から get: ok
    println(a.setterVisibility) // abc
    a.other = 1
    println(a.setterVisibility) // 2

    // 外部から set: コンパイルエラー
    a.setterVisibility = ""    // Cannot assign to 'setterVisibility': the setter is private in 'A'
}

java の getter, setter を呼び出す場合

Java Interop - Calling Java from Kotlin - Getters and Setters 参照

Java conventions for getters and setters (...) are represented as properties in Kotlin

if the Java class only has a setter, it will not be visible as a property in Kotlin

  • java 側が
    1. getter + setter 両方 定義
      • Kotlin では var 扱い
        • -> 第15章-2 (p246) 参照 (.text <= .getText(), .setText(...))
    2. getter のみ 定義(setter が存在しない)
      • Kotlin では val 扱い
        • -> 第15章-4 (p258) 参照 (.classLoader <= .getClassLoader())
    3. setter のみ 定義
      • Kotlin では Property の記法ができない
      • java と同じように メソッド呼び出し
        • -> 第15章-2 (p246) 参照 (.setBackgroundColor(...))
import java.util.*

fun main(args: Array<String>) {
    var c = Calendar.getInstance()

    // java側で getter + setter 両方 定義
    c.time = Date()            // setTime(Date)
    println(c.time)            // getTime()

    // boolean は "is..."
    c.isLenient = true         // setLenient(boolean)
    println(c.isLenient)       // isLenient()

    // java側で getter のみ 定義
    println(c.calendarType)    // getCalendarType()
    c.calendarType = ""        // コンパイルエラー (Val cannot be reassigned)

    // java側で setter のみ 定義
    var lb = Locale.Builder()
    lb.setLocale(Locale.JAPAN) // setLocale(Locale)
    lb.locale = Locale.JAPAN   // コンパイルエラー (Unresolved reference: locale)
}
  • [補足]
  • java の getter (getXxx(), isXxx())
    • "get" から名前が始まって, 引数無し のメソッド
    • "is" から名前が始まって, 引数無し のメソッド (戻り値が boolean)
  • java の setter (setXxx(...))
    • "set" から名前が始まって, 引数1個 のメソッド

3.1 Backing Field

Classes and Objects - Properties and Fields - Getters and Setters - Backing Fields 参照

list 8.8 custom setter の例

  • 赤べこ本では list 8.8 custom setter で field の使用例を説明していますが, getter でも 使用可能
    • 以下の 一番最後の例 参照
// ok (list 8.6, 8.7 に載っている パターン)
class Val_Get {
    val p
        get() = "p" + other
    val other = ""
}

// コンパイルエラー
class Val_Get_Field {
    val p
        get() = "p" + other + field // Unresolved reference: field
    val other = ""
}

// コンパイルエラー
class Val_Init_Get {
    val p = "p"  // Initializer is not allowed here because this property has no backing field
        get() = "p" + other
    val other = ""
}

// ok
class Val_Init_Get_Field {
    val p = "p"
        get() = "p" + other + field
    val other = ""
}

val + get() 」 の例 パターン一覧 (var も検証しようとしたけれど面倒になったので割愛..)

No. val/var initializer 指定 get() field 指定 set() field 指定 backing field 自動生成 コンパイル成功/失敗
1. val 無(✖️) 有(⭕️) 無(✖️) - - 無(✖️) 成功(⭕️)
2. val 無(✖️) 有(⭕️) 有(⭕️) - - - 失敗(✖️)
3. val 有(⭕️) 有(⭕️) 無(✖️) - - - 失敗(✖️)
4. val 有(⭕️) 有(⭕️) 有(⭕️) - - 有(⭕️) 成功(⭕️)

3.2 遅い初期化

Classes and Objects - Properties and Fields - Late-Initialized Properties 参照

not in the primary constructor

[1] コンストラクタ に指定できない

  • インスタンス生成(コンストラクタ実行)の後に初期化する property のため
// コンパイルエラー
class MyClass(lateinit var foo: String)  // 'lateinit' modifier is not allowed on primary constructor parameters

only when the property does not have a custom getter or setter

[2] custom getter or setter を定義できない

class MyClass {
    // コンパイルエラー
    lateinit var foo: String // 'lateinit' modifier is not allowed on properties with a custom getter or setter
        get() = "" + field
        set(value) {
            field = value
        }
}

must be non-null

[3] Nullable type を定義できない

  • 「 初期化される前に アクセスすると 例外発生 」の判断基準が 「 object が null かどうか 」で
  • Kotlin のプログラム上で 「 後から null 代入可能 」にしてしまうと
  • 「初期化していないから null」なのか「 後から null 」なのか区別ができないため ??
class MyClass {
    // コンパイルエラー
    lateinit var foo: String? // 'lateinit' modifier is not allowed on nullable properties
}

must not be a primitive type

[4] primitive type を定義できない

Kotlin Int -> java int
Kotlin Int? -> java Integer

One important remark: when using generic classes, such as collections, even non-null types will be boxed, 
  • java の primitive type は 値型(0, false などの 値 で初期化される) で null 代入できないため、
  • 上述の must be non-null と同じ理由で 以下のクラスは全てコンパイルエラー
    • boolean
    • byte
    • short
    • int
    • long
    • char
    • float
    • double
class MyClass {
    // 以下は 全て ok
    lateinit var foo: String
    lateinit var ai: Array<Int>

    // 以下は 全て コンパイルエラー
    lateinit var b: Boolean  // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var by: Byte    // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var s: Short    // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var i: Int      // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var l: Long     // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var c: Char     // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var f: Float    // 'lateinit' modifier is not allowed on primitive type properties
    lateinit var d: Double   // 'lateinit' modifier is not allowed on primitive type properties
}
  • by Delegates.notNull() を使えば primitive type でも遅延初期化可能.
  • DI では使用できない
  • Reflectionを使用している -> lateinit よりパフォーマンス良くない
  • Examples には掲載されているが, reference には載ってない ??
import kotlin.properties.Delegates

class MyClass {
    var i: Int by Delegates.notNull()
}

fun main(args: Array<String>) {
    var c = MyClass()
    c.i = 999    // インスタンス生成後に 初期化
    println(c.i) // 999
}

lazy

Classes and Objects - Delegated Properties - Standard Delegates - Lazy 参照

class MyClass {
    val i: Int by lazy { 999 }
}

fun main(args: Array<String>) {
    var c = MyClass()
    // プロパティ の 初めてのアクセス時に 初期化
    println(c.i) // 999
}

-> 赤べこ本 第15章-2 (p249) 参照

これにより、各プロパティが val かつ NotNull になり
  • lazy にすると val で定義できる

 

lazylateinit var の 使い分け 注意

-> 八木 on Twitter: "Kotlinクイズ: ActivityのViewDataBindingはlazyにするけどFragmentではlateinitにします。なぜでしょう。" 参照

-> 八木 on Twitter: "答え言ってなかった。 lazyはvalなので再代入できない & FragmentはonDestroyViewのあとまたonCreateViewが走る可能性がある。そのためFragmentではlayoutはvarで持たないといけない 参照

 


5. Constructor と Initializer

list 8.13 コンストラクタ引数 にも デフォルト値 を設定可能

primary constructor の 全ての引数 に デフォルト値

Classes and Objects - Classes and Inheritance - Classes - Constructors 参照

NOTE: On the JVM, 
if all of the parameters of the primary constructor have default values, 
the compiler will generate an additional parameterless constructor 
which will use the default values.

This makes it easier to use Kotlin with libraries such as Jackson or JPA 
that create class instances through parameterless constructors.
  • primary constructor の 全ての引数 に デフォルト値 を指定 すると、
    • 引数無し constructor (全ての引数 が デフォルト値 を利用) が生成される。
    • 引数無し constructor を通して, インスタンス生成するライブラリ( Jackson や JPA など ) が使いやすく
  • 引数無し constructor を生成する compiler plugin があるので, 意識しなくても大丈夫か ??
JPA
The plugin specifies @Entity and @Embeddable annotations as markers that no-arg constructor should be generated for a class.
Jackson

KotlinとSpring BootとDoma2でAPIサーバーを作る #m3kt (p26 〜) 参照


6. Extension

拡張関数 は レシーバを第一引数にとる(パッケージレベルの場合は, 静的な)関数

-> 赤べこ本 p308 参照

拡張関数はコンパイルすると、レシーバーを第1引数として取るメソッドに ...(以下略)
パッケージレベルで定義された関数は、 ...(中略) static メソッドとしてコンパイルされる ...(以下略)

 

-> 4. 拡張関数の正体 · Anatomy Kotlin 参照

拡張関数の正体は、レシーバを第一引数にとる静的な関数だったのです

 

-> CA.ktでJavaに無い機能をKotlinがどう実現してるのか話してきました。 - MA Blog 参照

Package Level Extension Function
...(中略)
FileNameKt クラスの static method としてコンパイルされます。引数は拡張対象です。

Class Level Extension Function
...(中略)
この場合は Foo クラスの method としてコンパイルされます

 

  • 同じ .kt ファイルの パッケージレベル や、 同じ class の中で
  • 以下の2つを 同時に定義 すると コンパイルエラー
    • list 8.15 String を 1個 引数にとる function: countWords
    • list 8.18 String に対する 引数 0個 の extension function: countWords
  • 両方共に String を 1個 引数にとる 関数名=countWords の (パッケージレベルの場合は, 静的な)関数 に変換されるため
// list 8.15 `String` を引数にとる function
fun countWords(s: String): Int =
        s.split("""\s""".toRegex()).size

// list 8.18 `String` に対する extension function
fun String.countWords(): Int =
        this.split("""\s""".toRegex()).size
Platform declaration clash: The following declarations have the same JVM signature (countWords(Ljava/lang/String;)I):
    fun countWords(s: String): Int defined in root package in file sample.kt
    fun String.countWords(): Int defined in root package in file sample.kt
  • 別々の .kt ファイル(属する class が分かれる) や、 別々の class に 1個ずつ定義を分ければ コンパイルok
  • 別の fun の中に 2個 両方入れて ローカル関数 にしても コンパイルok.

Classes and Objects - Extensions - Extensions are resolved statically 参照

extension functions are dispatched statically,

拡張関数 は 静的 に解決される。 override できない。(ポリモーフィズム できない)

// reference のコード例を少し改修. C の 親class(A) を追加
open class A

open class C : A() {
    open fun foo_ov() = "C: member: Super Class"  // override 確認用
}
class D : C() {
    override fun foo_ov() = "D: member: override" // override 確認用
}

// `resolved statically` 確認用
fun C.foo() = "C: extension: Super Class"
fun D.foo() = "D: extension"

fun A.foo2() = "A: extension: Super Class"
fun D.foo2() = "D: extension"

fun main(args: Array<String>) {
    printOverride(D())   // D: member: override
    printFoo(D())        // C: extension: Super Class
    printFoo2(D())       // A: extension: Super Class
}

// override 確認用
fun printOverride(c: C) {
    println(c.foo_ov())
}
// `resolved statically` 確認用
fun printFoo(c: C) {
    println(c.foo())
}
fun printFoo2(c: C) {
    println(c.foo2()) // `foo2` が 自クラス(C) に無いので 親クラス(A, Any)から探す
}

member always wins

member と extension が同じシグニチャの場合は member の方が優先される

// reference のコード例を少し改修.
open class C {
    // `member always wins` 確認用
    fun foo() = println("member")
    fun foo2() = println("member -> ${bar()} 呼び出し可能")
    private fun pri() = "private member"
}

fun C.bar() = "extension"
// `member always wins` 確認用 (以下の extension は呼ばれない)
fun C.foo() = println("extension")
fun C.foo2() = println("extension") // + pri() // Cannot access 'pri': it is private in 'C' (extension -> private は呼べない)

fun main(args: Array<String>) {
    C().foo()    // member
    C().foo2()   // member -> extension 呼び出し可能
}

Classes and Objects - Extensions - Nullable Receiver 参照

can be defined with a nullable receiver type

nullable type も 拡張できる.

  • Any?.toString() は標準で定義されている
fun main(args: Array<String>) {
    val a: String = "test"
    a.length

    val b: String? = null
    b.length     // コンパイルエラー (Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?)
    b?.length    // 第12章 Null 安全
    b.toString() // 正常. (Any?.toString() が定義されているため)

    a.ext() // not null.
    b.ext() // null !!
}

fun String?.ext() = if (this == null) println("null !!") else println("not null.")

補足: 任意の型 T に対する拡張関数

  • -> 第11章 ジェネリックス 参照

koher さんの Qiita記事 「 JavaプログラマがKotlinでつまづきがちなところ 参照

Any と Any?

...(中略)
T が Nullable Type を表すことができるからです。
型パラメータは制約を付けなければ Nullable Type を含めて
すべての型のプレースホルダーとして働きます
(デフォルトで upper bound が Any? )
  • -> 第12章-4 安全呼び出し list 12.10 拡張関数 let の定義 (p179) 参照
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  • let は Nullable Type 含めて 任意の型から呼び出せる
fun main(args: Array<String>) {
    val a: String = "test"
    val b: String? = null

    a.let { println(it) }  // test
    b.let { println(it) }  // null

    a.ext() // not null.
    b.ext() // null !!
}

fun <T> T.ext() = if (this == null) println("null !!") else println("not null.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment