Kotlinのリファレンスを読んでいる
諸事情でKotlinを覚えるかとなったので
速習 Kotlin: Javaより簡単!新Android開発言語を今すぐマスター 速習シリーズ
- 作者: 山田祥寛
- 出版社/メーカー: WINGSプロジェクト
- 発売日: 2018/09/26
- メディア: Kindle版
- この商品を含むブログを見る
とりあえずこれをざっと読んだけど結局リファレンスを読んでいる この本はJavaわかれば電車とかでびゃっと読めてなんとなくKotlinかけるようになると思うのでやる気あんまないけどざっくり把握したいみたいな時には良さそう try式ないとかあったのでなんとなく書くレベルで必要なのが網羅できてるかというとなんとも言えない
やる気があるならリファレンス読めば良さそう だいたいのことがそう
どこまで読んだか忘れちゃうのでメモレベルのメモ
Control Flow: if, when, for, while
- ifが式なので三項演算子ない
- if式はelseないとだめ
- when(switchみたいの)もそう
- 引数なしwhenはif-else ifのノリで使える
- else ifだらけになる場合こっちのが良さそう
Returns and Jumps
Classes and Inheritance
- プライマリコンストラクタはbodyを持てないので、initブロックで初期化処理を書く
- initブロックは複数かける。書いた順に実行される
- initブロックではプライマリコンストラクタの引数を参照できる
- プライマリコンストラクタの引数はクラスのフィールドになる
- valで宣言した引数はread-onlyなフィールドになる
fun main() { var test = Test("test") println(test.value) } class Test(var value:String){ init { println("You can read property defining at Primary Constructor. $value") } init { println("You can define multiple initializer") } }
- 全てのinitializerはセカンダリコンストラクタよりも前に実行される
- プライマリコンストラクタがないとデフォルトコンストラクタを生成される。デフォルトコンストラクタを露出せずプライマリコンストラクタを定義したくない場合、引数なしでprivateなプライマリコンストラクタを定義する
- プライマリコンストラクタの引数が全てデフォルトを持っていると、JVM上ではそのデフォルトを使う引数なしコンストラクタを生成する
- 全てのクラスはAnyを継承している
- 継承する場合は継承元のコンストラクタで初期化してあげること
open class Super(val a:Int, val b:Int) class Child1(a2:Int, b2:Int): Super(a2,b2) class Child2: Super { constructor():super(1,2) }
- オーバーライドできる要素にはopenを明示する必要がある
- overrideしたメソッドを、さらに継承したクラスでoverrideするのを防ぐ場合はfinal
open class Super { open fun method() { println("parent") } } open class Child1 :Super(){ override fun method() { super.method() println("child1") } } class Child2 :Child1(){ override fun method() { super.method() println("child2") } } fun main(args: Array<String>) { Child2().method() }
- プロパティもオーバーライドできる
- あんまり用途がわかってない
- カスタムアクセサの拡張とかだろうか🍙
- 派生クラスの初期化よりもベースクラスの初期化のが先に走る
- ベースクラスのinitializerでopenなメンバを使わない
- 派生クラスでoverrideしたメンバが初期化されてないのにinitializerで呼ばれる形になるので、予期しない動きをする可能性がある
- ベースクラスが複数あって同じメンバが存在したらオーバーライド必須
- クラスはstaticなメソッドを持てない
- トップレベル関数を定義しよう
- クラス経由で関数を呼びたいが、初期化はしない場合はオブジェクト宣言orコンパニオンオブジェクト
Properties and Fields
- backing fieldの参照はカスタムアクセサを介さない
- カスタムアクセサの中でフィールドを参照すると再帰になって死ぬ
- backing propertiesの用途がよくわからない
- lateinitをつけた変数はインスタンス生成時に初期化しなくてもコンパイルエラーにならない
- ただし、初期化せずにコールすると例外
- DIとかの文脈で使うらしい
- Setterインジェクションとか?
- Nullableでnullで初期化するより取り回しいいのかな?
interfaces
- インターフェースはプロパティをもてる
- が、abstructかカスタムアクセサの実装のみ
- backing fieldはもてないし、カスタムアクセサから触れない
- 複数のインターフェースを実装した時にシグネチャが被るメンバがあったらそいつはオーバーライドしないといけない
Visibility Modifiers
- トップレベルに関数を定義できる
- トップレベルの要素に対するVisibility Modifiers
- privateはfile内から参照、internalはモジュール内から参照できる。protectedはつけられない
- クラスのメンバに対するVisibility Modifiers
- privateはクラス内、protectedはそのクラスとサブクラスから、internalはモジュール内から参照
- モジュールはまとめてコンパイルした組み合わせのこと?🍙
Extensions
- こんな感じですでにあるクラスを拡張できる
fun String.yeah(){ println(this) println("yeah") } fun main(args: Array<String>) { "oh".yeah() }
- メンバと拡張関数のシグネチャが同じだとメンバが優先される
- ハマりそう
- Nullable型を拡張するとレシーバがNullableになる
- レシーバがnullでも呼べる
- 拡張プロパティ
- カスタムアクセサだけ
- プロパティとかフィールドとかの言葉の定義を一回整理したい🍙
- クラスのなかで他のクラスを拡張できる
- 定義したクラスの中でだけ有効っぽい
class C (val value:Int) class D { fun C.a(){ println(this.value) } fun b(c:C){ c.a() } } fun main(args: Array<String>) { D().b(C(1)) }
Data Classes
- Lombokの@Dataみたいな
- 生成されたメソッドはprimary constructorで定義したメンバしか使わない
Generics
- 型パラメータが推論できる場合は、コンストラクタ呼び出し時に省略できる
class A
fun main(args: Array
- in/outはこの記事がわかりやすかった
- inは型パラメータが引数の型にだけ使われるマーク
だったら、Tが引数の型のメソッドがある - その型パラメータが設定されたクラスのメソッドは、Tの継承クラスを渡せるはず
- outは型パラメータが戻り値の型にだけ使われるマーク
- 戻り値を、そのクラスの親としてそのまま格納できる
- ちょっとなんか怪しいのであとでもう一度🍙
Nested and Inner Classes
- ネストされたクラスにinnerがついてるとouterを参照できる
class A (val value:Int){ inner class B { fun print() { print(value) } } }
- 匿名クラスはobjectつける
interface A { fun print() } abstract class B { abstract fun print() } fun main(args: Array<String>) { val a = object : A { override fun print() { print("test") } } a.print() val b = object : B() { override fun print() { print("test") } } b.print() }
- Sam変換微妙に理解できてない🍙
- Javaの関数型インターフェースにのみ使えるっぽい
fun test(s:Comparator<Int>) { s.compare(1,2) } fun main(args: Array<String>) { test(Comparator { o1, o2 -> o2 - o1 }) test({ o1, o2 -> o2 - o1 }) // これなんでだめ? }
Enum Classes
- こんなんできる
enum class Test { A { override fun print() { println("AAAA") } }, B { override fun print() { println("BBBB") } }; abstract fun print() } fun main(args: Array<String>) { Test.A.print() Test.B.print() }
- interfaceを実装できる
- メンバはclassに定義してもいいし、enum値に定義してもいい
interface A { fun A() } interface B { fun B() } enum class E:A,B { ENUM{ override fun B() { println("BBBB") } }; override fun A() { println("AAAA") } } fun main(args: Array<String>) { E.ENUM.A() E.ENUM.B() }
Object Expressions and Declarations
- なんかの方を継承したanonymous classのインスタンス生成には、object式を使う
- object式に複数の型を渡す奴が何なのかよくわからん🍙
- 匿名オブジェクトをpublicな関数で返すと、そのオブジェクトのメンバにはアクセスできない
- クラスっぽい感じでobjectを宣言するとシングルトンになる
- スレッドセーフ
- 初期化はスレッドセーフ
- 何かのクラスを継承できる
- クラス内のオブジェクトはcompanionで宣言できる
- コンパニオンオブジェクトがあんまり理解できてない?🍙
Type aliases
- 既存の型にエイリアスをつけられる
typealias Maaaap = Map<String, Map<String, String>> fun main(args: Array<String>) { val map : Maaaap = mapOf("p" to mapOf("c" to "v")) }
- 関数型のエイリアス
typealias F = (Int) -> Int fun main(args: Array<String>) { var a:F = {a:Int -> a} }
- ネストされたクラスにも行ける
- インナークラスとネストされたクラスの違いが理解できてない🍙
- あくまでエイリアスで同様の型として扱われる
typealias Integer = String fun method(value:String) { print(value) } fun main(args: Array<String>) { val ok:Integer = "YEAH" method(ok) }
Inline classes
- こういう使い方であってるんだろうか
class Yes { fun yes(){ println("yes") } } inline class LoggingYes(val value: Yes) { fun yes(){ log.info("--- yes start ---") value.yes() log.info("--- yes end ---") } }
- 普通のクラスとの違いがよくわからない🍙
- これはパフォーマンスの話だけ?
Delegation
- byに指定したプロパティの引数をオブジェクトでインターナルに保存し、そのクラスが持っている全てのメンバをbyが指定されたクラスのメンバとして扱える
Delegated Properties
- カスタムアクセサの共通化のためにgetterとsetterを定義したクラスを作ってbyで委譲できる
- lazyにLambdaを渡すと遅延評価できる
import kotlin.reflect.KProperty class Data { var count:Int = 0 val a:String by lazy { println("compute") count++ "ohhhhhhhhhhhhhhhhhhhh" } } fun main(args: Array<String>) { val e = Data() println(e.count) // 0 println(e.a) // ここでlazyに定義したLambdaが計算される println(e.count) // 1 println(e.a) // 2回目はそのまま返す println(e.count) // 1 }
- Delegates.observableはsetterの処理を委譲
- 型と変更前、変更後の値が参照可能
import kotlin.properties.Delegates class Data { var a:String by Delegates.observable("initial"){ property, oldValue, newValue -> println("$property from $oldValue to $newValue") } } fun main(args: Array<String>) { val e = Data() e.a = "a" // var Data.a: kotlin.String from initial to a }
- vetoableはbooleanを返すラムダを定義してfalseを返すと値を更新しない
import kotlin.properties.Delegates class Data { var a:String by Delegates.vetoable("initial"){ property, oldValue, newValue -> println("$property from $oldValue to $newValue") if (newValue == "fuck") { false } else { true } } } fun main(args: Array<String>) { val e = Data() e.a = "a" println(e.a) e.a = "fuck" // この代入は無視される println(e.a) // a }
- なぜmapに委譲できるのかよくわからない 🍙
- 委譲プロパティの要件を満たしてないように見えてる
- local変数も委譲プロパティ使える
- 使い所難しい気もする
- provideDelegate理解できてない 🍙
Functions
- デフォルトの引数は継承しても引き継ぐ
open class Yes { open fun hello(value:String = "hello") { println(value) } } class Yes2: Yes() { override fun hello(value: String) { super.hello(value) println("hello2") } } fun main(args: Array<String>) { Yes2().hello(); }
- 戻り値が不要な場合、戻り値はUnitとなる
- この時の型の指定はOptional
- 1つの式だけの関数は中括弧を省略できる
fun a(a:Int, b:Int) = a * b fun main(args: Array<String>) { println(a(2, 3)) // 6 }
- 戻り値の型は推論されない
- 可変長引数はvararg
- infix function
infix fun String.join(value:String) = this + value fun main(args: Array<String>) { println("YEAH" join "OHHHHHHHHHH") }
- ローカルな関数定義ができる
- あんまり用途わかってない
fun main(args: Array<String>) { fun yes() { println("yes") } yes() }
Higher-Order Functions and Lambdas
- 関数型は関数型インターフェースじゃなくてこんな感じ
val sum1 = { x: Int, y: Int -> x + y } val sum2:(Int, Int) -> Int = { x,y -> x + y } fun main(args: Array<String>) { println(sum1.invoke(1, 2)) println(sum2.invoke(1, 2)) }
- 関数を引数にとる関数を高階関数という
fun test(text:String, func: (String) -> String){ println(text) println(func(text)) } fun main(args: Array<String>) { test("yes", {it + "func"}) // 引数が一つならitで参照できる test("yes"){it + "func"} // 関数型の引数が最後なら外に出せる }
- 無名関数だとこうなる
fun test(text:String, func: (String) -> String){ println(text) println(func(text)) } fun main(args: Array<String>) { var a = fun(a:String): String { return a + "func"; } test("yes", a) } * 外側のスコープの変数にアクセスできるし、変更もできる fun main(args: Array<String>) { var a = 1 var b = {println("b");a = 100} println(a) // 1 b() // bの関数をコール println(a) // 100 }
fun main(args: Array<String>) { val join = fun String.(arg:String): String = this + arg // レシーバ付き関数は println("yes".join(" no")) // yes no // このノリで使える }