Open11

速習Kotlin 読書メモ

だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 2: 基本構文

  • Any型の変数は、途中で別の型を代入できる
実験コード
package com.example.kotlinlearning

fun main() {
    var num: Any = 10
    println(num)
    println(num.javaClass)
    println(num::class)
    num = "aaa"
    println(num)
    println(num.javaClass)
    println(num::class)
}

出力

10
class java.lang.Integer
class java.lang.Integer (Kotlin reflection is not available)
aaa
class java.lang.String
class java.lang.String (Kotlin reflection is not available)
  • nullチェックの後はスマートキャストされる
実験コード

だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 3: 制御構文

  • ifは式として扱えるので、そのまま変数に代入できる。

    • ただし式として扱うときはelseが必要(何かしら値を返すため)
  • when(switch文的な)も式として扱える

実験コード
fun main() {
    val obj: Any = "aaa"

    when(obj) {
        is Int -> println("Int")
        is String -> println("String")
    }

    val msg = when(obj) {
        is Int -> "Int"
        is String -> "String"
        else -> "Unknown"
    }

    println(msg)
}

出力

String
String
  • 入れ子になっているループで、外側のループを抜ける場合は @outerを利用する
実験コード(continue, break, break@outer)
  • continue
fun main() {
    for (i in 1..10) {
        if (i % 3 == 0) continue
        println(i)
    }
}

出力

1
2
4
5
7
8
10
  • 通常のbreak
fun main() {
    for (i in 1..10) {
        for (j in 1..10) {
            if (i * j >= 30) break
            print("${i * j} ")
        }
        println()
    }
}

出力

1 2 3 4 5 6 7 8 9 10 
2 4 6 8 10 12 14 16 18 20 
3 6 9 12 15 18 21 24 27 
4 8 12 16 20 24 28 
5 10 15 20 25 
6 12 18 24 
7 14 21 28 
8 16 24 
9 18 27 
10 20
  • outerの例
fun main() {
    outer@ for (i in 1..10) {
        for (j in 1..10) {
            if (i * j >= 30) break@outer
            print("${i * j} ")
        }
        println()
    }
}

出力

1 2 3 4 5 6 7 8 9 10 
2 4 6 8 10 12 14 16 18 20 
3 6 9 12 15 18 21 24 27
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 4: 関数

高階関数

  • 関数そのものを引数として受け渡す
  • ::関数名で関数への参照を引き渡す
::関数名の実験
fun main() {
    val list = intArrayOf(2, 3)

    println("=== list.forEach(::myPrint) ===")
    // メソッド参照
    list.forEach(::myPrint)

    println("=== list.forEach { item -> myPrint(item) } ===")
    // ラムダ式
    list.forEach { item -> myPrint(item) }

    // 引数が単一の場合はitで受け取る
    println("=== list.forEach { myPrint(it) } ===")
    list.forEach { myPrint(it) }
}

fun myPrint(item: Int) {
    println("myPrint: $item")
}

出力

=== list.forEach(::myPrint) ===
myPrint: 2
myPrint: 3
=== list.forEach { item -> myPrint(item) } ===
myPrint: 2
myPrint: 3
=== list.forEach { myPrint(it) } ===
myPrint: 2
myPrint: 3
  • ()の省略など
package com.example.kotlinlearning

fun main() {
    val list = intArrayOf(2, 3)
    // 最も省略しない形
    list.forEach({ num -> println(num) })
    // 最後の引数がラムダ式であれば、ラムダ式をカッコの外に出すことが可能
    list.forEach() { num -> println(num) }
    // 引数がラムダ式のみであれば、カッコを省略可能
    list.forEach { num -> println(num) }
    // 引数が1つの場合、itで省略可能
    list.forEach { println(it) }
}
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 5: オブジェクト指向構文

  • プロパティ
    • setterの設定値は、慣例的に引数名はvalueだが、何でも良い。
    • fieldはバッキングフィールド
アクセサーを伴うプロパティ
fun main() {
    val p = Person()
    p.age = 20
    p.show()
    p.age = 17
    p.show()
}

class Person() {
    val isAdult
        get() = age >= 18

    var age = 18
        set(value) {
            if (value < 18) {
                throw IllegalArgumentException("年齢は18歳以上にしてください")
            }
            field = value
        }

    fun show() {
        println("年齢は${age}です")
        println("成人ですか?: ${isAdult}")
    }
}

出力

年齢は20です
成人ですか?: true
Exception in thread "main" java.lang.IllegalArgumentException: 年齢は18歳以上にしてください
プライマリコンストラクター
fun main() {
    val p = Person("たろう", 20)
    p.show()
}

class Person(name: String, age: Int) {
    val name: String
    val age: Int

    // 初期化ブロック
    init {
        this.name = name
        this.age = age
    }

    fun show() {
        println("名前は${name}です。年齢は${age}です。")
    }
}

出力

名前はたろうです。年齢は20です。
  • initブロックを使わずにプロパティに渡すことも出来るが、Property is explicitly assigned to parameter name, so it can be declared directly in the constructor と言われる。
class Person(name: String, age: Int) {
    val name: String = name
    val age: Int = age

    fun show() {
        println("名前は${name}です。年齢は${age}です。")
    }
}
  • コンストラクタでプロパティを定義
class Person(val name: String, val age: Int) {

    fun show() {
        println("名前は${name}です。年齢は${age}です。")
    }
}
セカンダリコンストラクター
fun main() {
    val p = Person()
    p.show() 
}

class Person(private val name: String, private val age: Int) {
    constructor(name: String) :this(name, 18)
    constructor() :this("名無し")

    fun show() {
        println("名前は${name}です。年齢は${age}です。")
    }
}

出力

名前は名無しです。年齢は18です。
プライマリコンストラクターの引数の既定値でもっとすっきり
fun main() {
    val p = Person()
    p.show()
}

class Person(private val name: String = "名無し", private val age: Int = 18) {

    fun show() {
        println("名前は${name}です。年齢は${age}です。")
    }
}

出力

名前は名無しです。年齢は18です。
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 6: 継承とインターフェイス

  • Kotlinでは規定でクラスは継承できない。継承させたい場合は明示的に open修飾子を付与する
    • メソッドも同様
  • メソッドをオーバーライドする際には、子のメソッドにoverride修飾子をつける(親にはopen
継承の例
package com.example.kotlinlearning

fun main() {
    val b = BusinessPerson("山田", "開発部")
    println(b.show())
}

open class Person(private val name: String = "名無し") {
    open fun show(): String {
        return "名前は${name}です。"
    }
}

class BusinessPerson(name: String, val depart: String) : Person(name) {
    override fun show(): String {
        return "${super.show()} 部署は${depart}です。"
    }
}

出力

名前は山田です。 部署は開発部です。
  • openは、オーバーライド「してもよい」だったのに対して、abstractは「しなければならない」となる。
抽象クラス、メソッド
fun main() {
    val t = Triangle(10.0, 5.0)
    println("三角形の面積は${t.getArea()}です")
}

abstract class Figure(val width: Double, val height: Double) {
    // てきとーにreturn 0.0をするのでは意味がない。なので実装を持たずに、abstractなメソッドとする
    abstract fun getArea(): Double
}

class Triangle(width: Double, height: Double) : Figure(width, height) {
    override fun getArea(): Double {
        return width * height / 2
    }
}

出力

三角形の面積は25.0です
インターフェイス
fun main() {
    val t = Triangle(10.0, 5.0)
    println("三角形の面積は${t.getArea()}です")
    t.hello()
}

interface Figure {
    // プロパティを定義可能(インターフェイスでは実装を持てないので、初期値は渡せない)
    var name: String

    // インターフェイスのデフォルト実装。プロパティを参照可能
    fun hello() {
        println("Hello, I'm $name.")
    }

    fun getArea(): Double
}

class Triangle(private val bottom: Double, private val height: Double) : Figure {
    override var name: String = "Triangle"

    override fun getArea(): Double {
        return bottom * height / 2
    }
}

出力

三角形の面積は25.0です
Hello, I'm Triangle.
  • Kotlinでは基本スマートキャストだけ使うと思ったほうがよい
キャストの実験コード
fun main() {
    val p: Person = BusinessPerson("山田", "開発部")
    println(p.show())

    if (p is BusinessPerson) {
        p.work() // スマートキャスト
    }

    // 明示的なキャスト。失敗する可能性もあるので、Unsafe Castと呼ばれる。
    (p as BusinessPerson).work()
}

open class Person(private val name: String = "名無し") {
    open fun show(): String {
        return "名前は${name}です。"
    }
}

class BusinessPerson(name: String, val depart: String) : Person(name) {
    override fun show(): String {
        return "${super.show()} 部署は${depart}です。"
    }

    fun work() {
        println("働いています。")
    }
}

出力

名前は山田です。 部署は開発部です。
働いています。
働いています。
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 7: 特殊なクラス(dataクラス)

データクラス
fun main() {
    val person = Person("太郎", 20)
    println(person)
}

data class Person(val name: String, val age: Int)

出力

Person(name=太郎, age=20)
  • dataクラスでは、==演算子によって、プライマリコンストラクターで定義されたプロパティが等しいかを判定される。
dataクラスと通常のクラスの比較
fun main() {
    val person1 = Person("太郎", 20)
    person1.height = 170
    val person2 = Person("太郎", 20)
    person2.height = 150
    println(person1 == person2) // true

    val normalPerson1 = NormalPerson("太郎", 20)
    val normalPerson2 = NormalPerson("太郎", 20)
    println(normalPerson1 == normalPerson2) // false
}

data class Person(val name: String, val age: Int) {
    var height: Int = 160
}

class NormalPerson(val name: String, val age: Int)

出力

true
false
分解代入
fun main() {
    val person = Person("太郎", 20)
    val (nameVal, ageVal) = person
    println("名前: $nameVal, 年齢: $ageVal")
}

data class Person(val name: String, val age: Int) {
    var height: Int = 160
}

出力

名前: 太郎, 年齢: 20
copyメソッド
  • プライマリコンストラクターの値以外はコピーされない
fun main() {
    val person = Person("太郎", 20)
    person.height = 170
    val personCopied = person.copy(age = 21)
    println(person)
    println(person.height)
    
    println(personCopied)
    println(personCopied.height)
}

data class Person(val name: String, val age: Int) {
    var height: Int = 160
}

出力

Person(name=太郎, age=20)
170
Person(name=太郎, age=21)
160
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 7: 特殊なクラス(オブジェクト宣言、

オブジェクト式

  • 以下のコードは、オブジェクト式をSAM変換によってラムダ式に書き換え、さらにラムダ式の書き方を簡略化した結果である。
// object: parent { ..body.. }で、その場限りのクラスを定義することができる。これがオブジェクト式
// SAM(Single Abstract Method)インターフェースは、1つの抽象メソッドしか持たないインターフェイスのことで、
// View.OnClickListenerインターフェースもSAMに該当
btn.setOnClickListener(object: View.OnClickListner {
    override fun onClick(view: View) {
        Log.v("Kotlin Sample", "Clicked!!")
    }
})

// SAM変換によって、ラムダ式で書くことができる。
btn.setOnClickListener({ view: View -> Log.v("Kotlin Sample", "Clicked!!") })

// ラムダ式の簡略化によってこのような記述になる
btn.setOnClickListener { Log.v("KotlinSample","Clicked!!") }

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

  • クラス内部でのオブジェクト宣言のこと。
  • Kotlinにはstaticメンバーは存在しないが、コンパニオンオブジェクトを用いることでその代替ができる。
実験コード
fun main() {
    val person = Person.getInstance("山田はなこ")
    println(person.name)
}

class Person private constructor(var name: String) {
    companion object {
        fun getInstance(name: String): Person {
            return Person(name)
        }
    }
}

出力

山田はなこ
  • companion objectに名前をつけない場合はデフォルトの名前になる。Factoryと名前をつけると意図が明確になる。
    • ただし、Person2.Factory.getInstance()とFactoryを挟むのは冗長と判断される。

  • コンパニオンオブジェクトはクラスに1つ
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 7: 特殊なクラス(Enumクラス、ジェネリック)

Enumクラス
fun main() {
    val s = Season.FALL
    println(s) // FALL
    println(s.name) // FALL
    println(s is Season) // true
    println(s is Enum<Season>) // true
    println(s.ordinal) // 2
}

enum class Season {
    SPRING,
    SUMMER,
    FALL,
    WINTER
}
  • プロパティやメソッドをもたせることが出来る
    • SUMMERなどの定義によってインスタンス化しているイメージ
    • っメソッドを定義するためには列挙定数の末尾を ;で終了し、メンバー定義との区切りを明確にする。
package com.example.kotlinlearning

fun main() {
    val summer = Season.SUMMER
    println(summer.value)
    summer.sayValue()
    summer.hello()

    val fall = Season.FALL
    println(fall.value)
    fall.sayValue()
    fall.hello()
}

enum class Season(val value: Int) {
    SPRING(1),
    SUMMER(2),
    FALL(4) {
        // オーバーライドできる
        override fun hello() {
            println("Hello, It's fall")
        }
    },
    WINTER(8);

    open fun hello() {
        println("Hello")
    }

    // プロパティにアクセス可能
    fun sayValue() {
        println("Value is $value")
    }
}

出力

2
Value is 2
Hello
4
Value is 4
Hello, It's fall
  • abstractの場合はちゃんと実装しないとコンパイルエラー

ジェネリック

実験コード
  • クラス名の後方に<T>のように型引数を定義する。
fun main() {
    val myGeneric = MyGeneric("abc")
    println(myGeneric.getProp()) // abc

    myGeneric.value = "def"
//    myGeneric.value = 123 // コンパイルエラー The integer literal does not conform to the expected type String
}

class MyGeneric<T>(var value: T) {
    fun getProp(): T {
        return value
    }
}
  • Tの型を制約する
fun main() {
    val hoge = Hoge()
    val myGenericHoge = MyGeneric(hoge)
    
    val hogeChild = HogeChild()
    val myGenericHogeChild = MyGeneric(hogeChild)
    
    val notHoge = NotHoge()
//    val myGenericNotHoge = MyGeneric(notHoge) // Type mismatch.
}

open class Hoge {}
class HogeChild: Hoge() {}
class NotHoge {}

class MyGeneric<T: Hoge>(var value: T) {}
// または以下のように記述
// class MyGeneric<T>(var value: T) where T: Hoge {}
ジェネリック関数
  • 関数名のtyぉく前に<T>で型引数を宣言する
  • ジェネリック関数は、非ジェネリックな型の中でも定義できる。そもそもクラスと独立して定義可能。
fun main() {
    val array = arrayOf(3, 4, 5)
    val myClass = MyClass()
    println(myClass.pickFirst(array)) // 3
}

class MyClass {
    fun <T> pickFirst(list: Array<T>): T {
        return list[0]
    }
}
  • トップレベルにも宣言できるからこれでもよい
fun main() {
    val array = arrayOf(3, 4, 5)
    println(pickFirst(array)) // 3
}

fun <T> pickFirst(list: Array<T>): T {
    return list[0]
}

不変・共変・反変

  • Kotlinの配列は不変(JavaやC#は共変)
実験コード
fun main() {
    // コンパイルエラー
    val anyArray: Array<Any> = arrayOf<String>(1, 2, 3)
}
public class Program
{
    public static void Main()
    {
        object[] objectArray = new string[] { "a", "b", "c" };
        objectArray[0] = 1; // コンパイルは通るが、ランタイムエラー
    }
}
KotlinのListは変更不可能、MutableListは変更可能
  • mutableListOf
fun main() {
    val list = mutableListOf("a", "b", "c")
    println(list[1]) // b
    list[1] = "d" // 変更可能
    println(list[1]) // d
}
  • listOf
fun main() {
    val list = listOf("a", "b", "c")
    println(list[1])
    list[1] = "d" // コンパイルエラー
}
共変が認められるList
fun main() {
    // コンパイルエラーなく通る
    val anyList: List<Any> = listOf<String>("a", "b", "c")
}
  • Listの定義を見ると、out修飾子がついている
public interface List<out E> : Collection<E> {
  • MutableListの方は、変更が可能であるためランタイムエラーが発生する可能性がある。そのため不変。
fun main() {
    // コンパイルエラー
    val anyList: MutableList<Any> = mutableListOf<String>("a", "b", "c")
}
  • MutableListの定義を見ると、out修飾子はついていない
public interface MutableList<E> : List<E>, MutableCollection<E> {
反変
  • in修飾子を利用することで、T型、もしくはその上位型」を認める
  • 比較をする機構(Comperator)はInt型に対応している必要がある。それはInt型でなくてもInt型を包含するInt型より上位の型であれば問題ない。そのため、ComparatorをInt型に限定する必要はなく、上位型を認めてもよい。
fun main() {
    val list = listOf(10, 2, 30, 4)
    // 当然Int型は渡せるが、
    println(list.sortedWith(ReverseComparatorInt()))

    // Any型でも渡せる(反変)
    println(list.sortedWith(ReverseComparatorAny()))
}

class ReverseComparatorAny : Comparator<Any> {
    override fun compare(x: Any, y: Any): Int = -1
}

class ReverseComparatorInt : Comparator<Int> {
    override fun compare(x: Int, y: Int): Int = -1
}
  • sortedWithメソッドの定義
public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {
  • String型は上位型ではないので渡せない
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 8: もっとオブジェクト指向

拡張関数
fun main() {
    val str = "Hello!"
    println(str.myRepeat(3)) // 出力: Hello!Hello!Hello!
}

fun String.myRepeat(num: Int): String {
    val builder = StringBuilder()
    for (i in 1..num) {
        builder.append(this)
    }
    return builder.toString()
}

入れ子クラス

入れ子クラス
  • 入れ子のクラスを含んだクラスのことをアウタークラスと呼ぶ(下の例ではMyClass)
  • 入れ子クラスは、アウタークラスのメンバーにアクセスできない。
fun main() {
    val myClass = MyClass("MyClass")
    myClass.callHelper() // 出力: Helping
    MyClass.MyHelper().help()  // 出力: Helping
}

class MyClass(val name: String) {
    class MyHelper {
        fun help() {
            println("Helping")
//            println(name) コンパイルエラー: Unresolved reference: name
        }
    }

    fun callHelper() {
        val helper = MyHelper()
        helper.help()
    }
}
  • MyHelperをMyClassプライベートとする場合は、MyHelper()の宣言時にprivate修飾子をつける

インナークラス

インナークラス
  • 入れ子となったクラスからアウタークラスのメンバーにアクセスしたい場合にはinner修飾子を付与する
fun main() {
    val myClass = MyClass("MyClass")
    myClass.callHelper()
    myClass.MyHelper("MyHelper").help()
}

class MyClass(val name: String) {
    inner class MyHelper(val name: String) {
        fun help() {
            println("====")
            println(name)
            println(this@MyClass.name)
        }
    }

    fun callHelper() {
        val helper = MyHelper("MyHelperFromMyClass")
        helper.help()
    }
}

出力

====
MyHelperFromMyClass
MyClass
====
MyHelper
MyClass
だーら(Flamers / Memotia)だーら(Flamers / Memotia)

Part 8: (続き)移譲プロパティ

実験コード
  • プロパティを変更しつつ、追加の処理を呼ぶ感じ?
import kotlin.properties.Delegates

fun main() {
    val p = Person()
    p.lastName = "太郎"
    p.printName()
    p.lastName = "次郎"
    p.printName()
}

class Person() {
    var lastName: String by Delegates.observable("初期値") {
        prop, prev, next -> println("${prop.name}${prev}から${next}に変わりました")
    }

    fun printName() {
        println(lastName)
    }
}

出力

lastNameが初期値から太郎に変わりました
太郎
lastNameが太郎から次郎に変わりました
次郎
vetoableで値を変更する前にチェックする
  • ラムダ式がfalseの場合は変更されない
import kotlin.properties.Delegates

fun main() {
    val p = Person()
    p.printAge() // 0
    p.age = 10
    p.printAge() // 10
    p.age = -1
    p.printAge() // 10
}

class Person() {
    // vetoとは、「拒否する」の意味
    var age: Int by Delegates.vetoable(0) {
        prop, old, new -> new >= 0
    }

    fun printAge() {
        println(age)
    }
}
lazyによる遅延プロパティ
fun main() {
    val myRandom = MyRandom()
    println("=== first time ==")
    println(myRandom.randomValue)
    println("=== second time ==")
    println(myRandom.randomValue)
}

class MyRandom {
    val randomValue: Double by lazy {
        println("Initializing")
        Math.random()
    }
}

出力

=== first time ==
Initializing
0.4276934288775911
=== second time ==
0.4276934288775911
自作移譲プロパティ
package com.example.kotlinlearning

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty


fun main() {
    val person = Person()
    println("=== println(person.name) ===")
    println(person.name)
    println("=== person.name = \"太郎\" ===")
    person.name = "太郎"
    println("=== println(person.name) ===")
    println(person.name)

    val book = Book()
    println("=== println(book.title) ===")
    println(book.title)
    println("=== book.title = \"速習Kotlin\" ===")
    book.title = "速習Kotlin"
    println("=== println(book.title) ===")
    println(book.title)
}

class LoggableProp<T>(var value: T): ReadWriteProperty<Any, T> {
    override fun getValue(thisRef: Any, prop: KProperty<*>): T {
        println("get ${prop.name}")
        println("thisRef: $thisRef::class.simpleName")
        return this.value
    }

    override fun setValue(thisRef: Any, prop: KProperty<*>, value: T) {
        println("set ${prop.name} to $value")
        this.value = value
    }
}

class Person {
    var name: String by LoggableProp("名無し")
}

class Book {
    var title: String by LoggableProp("無題")
}

出力

=== println(person.name) ===
get name
thisRef: com.example.kotlinlearning.Person@67b6d4ae::class.simpleName
名無し
=== person.name = "太郎" ===
set name to 太郎
=== println(person.name) ===
get name
thisRef: com.example.kotlinlearning.Person@67b6d4ae::class.simpleName
太郎
=== println(book.title) ===
get title
thisRef: com.example.kotlinlearning.Book@2c9f9fb0::class.simpleName
無題
=== book.title = "速習Kotlin" ===
set title to 速習Kotlin
=== println(book.title) ===
get title
thisRef: com.example.kotlinlearning.Book@2c9f9fb0::class.simpleName
速習Kotlin
移譲クラス
fun main() {
    val myClass = MyClass()
    myClass.log("Hello World")

    val myClass2 = MyClass2(LoggableImpl("MyClass2"))
    myClass2.log("Hello World")
}

interface Loggable {
    fun log(value: String)
}

class LoggableImpl(val header: String) : Loggable {
    override fun log(value: String) {
        println("${this.header}: $value")
    }
}

class MyClass : Loggable by LoggableImpl("MyClass") {}

// プライマリコンストラクタにすることで付け替えることもできる
class MyClass2(val log: Loggable) : Loggable by log {}