- Kotlinスタートブック読書勉強会 #05 第7章, 第8章 で最後に紹介した内容(一部, 読書会後に追記)
- 第7章 の 写経
- 第8章 の 写経
reference や他のサイトなどに記述があって、赤べこ本では割愛されている(または ver1.1 以降の)内容の引用 が中心
または 赤べこ本 第9章 以降 に掲載 されていて, 8章 のタイミングでついでに習った方が良さそうな内容
Classes and Objects - Properties and Fields - Getters and Setters 参照
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) 参照
get()
から 型推論 可能(Since 1.1)
val isEmpty get() = this.size == 0 // has type Boolean
// list 8.7 `: Int` 省略可
val nameLength2 // : Int
get() = this.name.length
可視性を変更 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 Interop - Calling Java from Kotlin - Getters and Setters 参照
- java 側が
- getter + setter 両方 定義
- Kotlin では
var
扱い- -> 第15章-2 (p246) 参照 (
.text
<=.getText()
,.setText(...)
)
- -> 第15章-2 (p246) 参照 (
- Kotlin では
- getter のみ 定義(setter が存在しない)
- Kotlin では
val
扱い- -> 第15章-4 (p258) 参照 (
.classLoader
<=.getClassLoader()
)
- -> 第15章-4 (p258) 参照 (
- Kotlin では
- setter のみ 定義
- Kotlin では Property の記法ができない
- java と同じように メソッド呼び出し
- -> 第15章-2 (p246) 参照 (
.setBackgroundColor(...)
)
- -> 第15章-2 (p246) 参照 (
- getter + setter 両方 定義
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個 のメソッド
Classes and Objects - Properties and Fields - Getters and Setters - Backing Fields 参照
- 赤べこ本では 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 |
有(⭕️) | 有(⭕️) | 有(⭕️) | - | - | 有(⭕️) | 成功(⭕️) |
Classes and Objects - Properties and Fields - Late-Initialized Properties 参照
[1] コンストラクタ に指定できない
- インスタンス生成(コンストラクタ実行)の後に初期化する property のため
// コンパイルエラー
class MyClass(lateinit var foo: String) // 'lateinit' modifier is not allowed on primary constructor parameters
[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
}
}
[3] Nullable type を定義できない
- 「 初期化される前に アクセスすると 例外発生 」の判断基準が 「 object が
null
かどうか 」で - Kotlin のプログラム上で 「 後から null 代入可能 」にしてしまうと
- 「初期化していないから
null
」なのか「 後からnull
」なのか区別ができないため ??
class MyClass {
// コンパイルエラー
lateinit var foo: String? // 'lateinit' modifier is not allowed on nullable properties
}
[4] primitive type を定義できない
- [前提知識]
- Kotlin の
Int
は java のint
(primitive type) に変換される - -> Relationship between classes like Kotlin Int and Java Integer (and int primitive) - Kotlin Discussions 参照
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
}
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
で定義できる
-> 八木 on Twitter: "Kotlinクイズ: ActivityのViewDataBindingはlazyにするけどFragmentではlateinitにします。なぜでしょう。" 参照
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 があるので, 意識しなくても大丈夫か ??
- Kotlin-jpa compiler plugin が用意されている
@Entity
,@Embeddable
付けているクラスが対象
The plugin specifies @Entity and @Embeddable annotations as markers that no-arg constructor should be generated for a class.
KotlinとSpring BootとDoma2でAPIサーバーを作る #m3kt (p26 〜) 参照
- jackson-module-kotlin が用意されている
-> 赤べこ本 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
- list 8.15
- 両方共に
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 参照
拡張関数 は 静的 に解決される。 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 と 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 参照
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.")
- -> 第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.")
- -> 第12章 の 補足 参照