Open48

Kotlin Examplesの学習メモ

netainetai

Hello World

package org.kotlinlang.play

fun main() {
    println("Hello, World!")
}
  • パッケージシステムはJavaと同じらしい
    • パッケージを省略するとデフォルトパッケージになる点も同じ
  • Javaとは違いクラスは不要。functionから始められる
    • エントリーポイントがmainなのは同じ
  • mainメソッドの引数は省略できる(Since Kotlin 1.3)
  • 標準出力はprintln関数
  • 文末セミコロンはOptional
fun main(args: Array<String>) {
    println("Hello, World!")
}
  • 1.3より前はmainの引数にargs: Array<String>が必須
  • 型宣言は変数名: 型

所感

ここまで見た感じ、JavaとPythonの間の子感がある(自分がそれらしか知らないだけで、もっと似てる言語があると思うが……)

netainetai

Functions

Default Parameter Values and Named Arguments

fun printMessage(message: String): Unit {
    println(message)
}

fun printMessageWithPrefix(message: String, prefix: String = "Info") {
    println("[$prefix] $message")
}

fun sum(x: Int, y: Int): Int {
    return x + y
}

fun multiply(x: Int, y: Int) = x * y

fun main() {
    printMessage("Hello")
    printMessageWithPrefix("Hello", "Log")
    printMessageWithPrefix("Hello")
    printMessageWithPrefix(prefix = "Log", message = "Hello")
    println(sum(1, 2))
    println(multiply(2, 4))
}
  • fun メソッド名(引数): 返り値型{ 処理 }が基本構文
  • 返り値型は省略でき、その場合はUnitを暗示する
  • =で引数のデフォルトを指定できる
  • 式が一つしか存在しない関数はreturnを省略できる(単一式関数; Single-Expression Method)
    • 返り値は型推論される
  • 引数名を明示することで、引数の順番に縛られず記述できる
netainetai

Infix Functions

fun main() {

  infix fun Int.times(str: String) = str.repeat(this)        // 1
  println(2 times "Bye ")                                    // 2

  val pair = "Ferrari" to "Katrina"                          // 3
  println(pair)

  infix fun String.onto(other: String) = Pair(this, other)   // 4
  val myPair = "McLaren" onto "Lucas"
  println(myPair)

  val sophia = Person("Sophia")
  val claudia = Person("Claudia")
  sophia likes claudia                                       // 5
}

class Person(val name: String) {
  val likedPeople = mutableListOf<Person>()
  infix fun likes(other: Person) { likedPeople.add(other) }  // 6
}
  • 中置(infix)記法というものが存在する
  • 1ではString型の引数を指定回繰り返すtimesというinfix functionを定義している
    • 2で2 times "Bye "のように記述している。これでBye Byeと出力されるようになる
    • (恐らく)自然言語風に読めるようになる、可読性が上がるのがメリット
  • 3は標準ライブラリのtoを使用しており、(Ferrari, Katrina)のPair型が出力される
  • 4はtoを手動で実装した場合の書き方
  • 5, 6はPerson型のクラスにlikeinfixを実装した場合。A like Bと書くだけで、likedPeopleにBがaddされる
netainetai

Operator Functions

operator fun Int.times(str: String) = str.repeat(this)       // 1
println(2 * "Bye ")                                          // 2

operator fun String.get(range: IntRange) = substring(range)  // 3
val str = "Always forgive your enemies; nothing annoys them so much."
println(str[0..14])                                          // 4
  • kotlinの演算子はオーバーロードすることで任意のクラスでも使える
  • 演算子に対応するfunctionにoperator修飾子を付与することで、オーバーロードを実装できる
  • 3は分かりづらいが、a.get(IntRange)a[IntRange]に対応しているので、strをsubstringで切り出すように[]をオーバーロードしたということ
netainetai

Functions with vararg Parameters

fun printAll(vararg messages: String) {                            // 1
    for (m in messages) println(m)
}
printAll("Hello", "Hallo", "Salut", "Hola", "你好")                 // 2

fun printAllWithPrefix(vararg messages: String, prefix: String) {  // 3
    for (m in messages) println(prefix + m)
}
printAllWithPrefix(
    "Hello", "Hallo", "Salut", "Hola", "你好",
    prefix = "Greeting: "                                          // 4
)
  • 引数にvararg修飾子を付けることで、可変長引数となる
  • Javaの可変長引数は必ず最後の引数である必要があったが、Kotlinでは名前付き引数(Named Parameter)を使うことで最後でなくてもよくなった(4)
fun log(vararg entries: String) {
    printAll(entries)                                             // 5
}
log(list) // NG (Type mismatch: inferred type is Array<out String> but String was expected)
  • varargは配列Array<out String>型という扱いなので、logの可変長引数として受けたentriesは、そのままではprintAllの引数として渡せない
fun log(vararg entries: String) {
    printAll(*entries)                                             // 5
}
log(list) // OK
  • *を付けることで、配列の内容を渡すことができるようになる
netainetai

Variables

var a: String = "initial"  // 1
println(a)
val b: Int = 1             // 2
val c = 3                  // 3
  • 変数宣言はvar/valを頭につけて行う
    • varはmutable、valはimmutable(Javaのfinal相当)
  • Kotlinでは強力な型推論機能が使えるため、3のようなコードもInt型として通る
netainetai

Null Safety

var neverNull: String = "This can't be null"            // 1

neverNull = null                                        // 2

var nullable: String? = "You can keep a null here"      // 3

nullable = null                                         // 4

var inferredNonNull = "The compiler assumes non-null"   // 5

inferredNonNull = null                                  // 6

fun strLength(notNull: String): Int {                   // 7
    return notNull.length
}

strLength(neverNull)                                    // 8
strLength(nullable)                                     // 9
  • Kotlinの変数型はnullを許容しない
    • よって、2のコードはエラーとなる(Null can not be a value of a non-null type String)
  • nullを許容するためには、?を型の後ろに付ける(3はnullable String型になり、4のコードが通るようになる)
  • 推論型(varval)もnullを許容しない(6はコンパイルエラー)
  • 7はnon-null Stringをパラメータとしたファンクションのため、9はコンパイルエラーとなる
    • じゃあどうすればいいのさ、は次で
netainetai

Working with Nulls

fun describeString(maybeString: String?): String {              // 1
    if (maybeString != null && maybeString.length > 0) {        // 2
        return "String of length ${maybeString.length}"
    } else {
        return "Empty or null string"                           // 3
    }
}
  • 外部のJavaコードとのやり取りなどで、nullが入り込む余地がある場合、functionにnullを許容させるためには、例のように記述する
  • Kotlinでは、条件分岐なくreturn "String of length ${maybeString.length}"だけを返すコードは通らないようになっており、nullを検査する必要がある
    • 分岐の&& maybeString.length > 0はなくても通る

fun describeString(maybeString: String?): String {              // 1
        return "String of length ${maybeString!!.length}"
}
  • 一応、!!.を用いてnullable String型をnon-null String型にキャストする方法もある
  • この場合、nullを代入するとぬるぽする(当然)
netainetai

Classes

class Customer                                  // 1

class Contact(val id: Int, var email: String)   // 2

fun main() {

    val customer = Customer()                   // 3
    
    val contact = Contact(1, "mary@gmail.com")  // 4

    println(contact.id)                         // 5
    contact.email = "jane@gmail.com"            // 6
}
  • クラス宣言はクラス名、丸括弧に書かれるクラスヘッダ、中括弧内に書かれるクラスボディの3つで構成される
    • この内、ヘッダとボディは省略できる
  • 1は最小限のクラスで、これでもデフォルトコンストラクタが自動で準備されている
  • 2はidとemailをコンストラクタのパラメータにしたクラス
  • 3や4のように、newは必要なくなった
    • 一応、Java風味な下のコードでも通る
    val customer: Customer = Customer()         // 3
netainetai

Generics

Generic Classes

class MutableStack<E>(vararg items: E) {              // 1

  private val elements = items.toMutableList()

  fun push(element: E) = elements.add(element)        // 2

  fun peek(): E = elements.last()                     // 3

  fun pop(): E = elements.removeAt(elements.size - 1)

  fun isEmpty() = elements.isEmpty()

  fun size() = elements.size

  override fun toString() = "MutableStack(${elements.joinToString()})"
}


  • 特定の型ではなく、使用時に型を決定できるジェネリッククラス
  • 特に語るところはないが、例はKotlinの省略記法が多用されているクラスになっている

Generic Functions

fun <E> mutableStackOf(vararg elements: E) = MutableStack(*elements)

fun main() {
  val stack = mutableStackOf(0.62, 3.14, 2.7)
  println(stack)
}
  • ジェネリックファンクションも用意されている
  • 例では型推論が働いてmutableStackOf<Double>が省略されている
netainetai

Inheritance

open class Dog {                // 1
    open fun sayHello() {       // 2
        println("wow wow!")
    }
}

class Yorkshire : Dog() {       // 3
    override fun sayHello() {   // 4
        println("wif wif!")
    }
}

fun main() {
    val dog: Dog = Yorkshire()
    dog.sayHello()
}
    1. openはクラス継承(ファンクションならオーバライド)可能の明示
    • nullableの?と同様に、明示しないと継承/オーバライドできない
    1. 継承はクラスに型宣言を行うような書き方。extendsではない
    • 空の括弧はスーパークラスのデフォルトコンストラクタを示す

つまり、コンストラクタの渡し方は以下の形になる

open class Dog(name: String){//処理}
class Yorkshire(name: String) : Dog(name){//処理}
    1. オーバライドする場合、override修飾子で明示

Inheritance with Parameterized Constructor

open class Tiger(val origin: String) {
    fun sayHello() {
        println("A tiger from $origin says: grrhhh!")
    }
}

class SiberianTiger : Tiger("Siberia")                  // 1

fun main() {
    val tiger: Tiger = SiberianTiger()
    tiger.sayHello()
}
  • サブクラスの宣言にスーパークラスのコンストラクタをパラメータ入りの状態で含めることができる

Passing Constructor Arguments to Superclass

open class Lion(val name: String, val origin: String) {
    fun sayHello() {
        println("$name, the lion from $origin says: graoh!")
    }
}

class Asiatic(name: String) : Lion(name = name, origin = "India") // 1

fun main() {
    val lion: Lion = Asiatic("Rufo")                              // 2
    lion.sayHello()
}
  • Asiaticの継承元Lionのコンストラクタの引数nameは、Lionのコンストラクタへの引数渡しを明示している
    • この状態でAsiaticクラスを生成するときは、"Rufo"だけをコンストラクタに入れることでname="Rufo、origin="India"のコンストラクタと解釈される
netainetai

When

When Statement

fun main() {
    cases("Hello")
    cases(1)
    cases(0L)
    cases(MyClass())
    cases("hello")
}

fun cases(obj: Any) {                                
    when (obj) {                                     // 1   
        1 -> println("One")                          // 2
        "Hello" -> println("Greeting")               // 3
        is Long -> println("Long")                   // 4
        !is String -> println("Not a string")        // 5
        else -> println("Unknown")                   // 6
    }   
}

class MyClass
  • Switch文の代わりになるWhen文が用意されている
  • casesメソッドの中にwhen文が入っており、その中に分岐先を用意する
    1. や 4. のように型チェックやその論理否定も使える
  • defaultの代わりにelseを用いる

When Expression

fun main() {
    println(whenAssign("Hello"))
    println(whenAssign(3.4))
    println(whenAssign(1))
    println(whenAssign(MyClass()))
}

fun whenAssign(obj: Any): Any {
    val result = when (obj) {                   // 1
        1 -> "one"                              // 2
        "Hello" -> 1                            // 3
        is Long -> false                        // 4
        else -> 42                              // 5
    }
    return result
}

class MyClass
  • 今度はresultに結果を代入する形の書き方(WhenExpression)
  • WhenExpressionはWhenStatementと違って、全てのケースをカバーできている場合を除いてdefault(else)が必須(コンパイルエラー)
netainetai

Loops

for

val cakes = listOf("carrot", "cheese", "chocolate")

for (cake in cakes) {                               // 1
    println("Yummy, it's a $cake cake!")
}
  • 基本構文がJavaの拡張for文風になっている

while and do-while

fun eatACake() = println("Eat a Cake")
fun bakeACake() = println("Bake a Cake")

fun main(args: Array<String>) {
    var cakesEaten = 0
    var cakesBaked = 0
    
    while (cakesEaten < 5) {                    // 1
        eatACake()
        cakesEaten ++
    }
    
    do {                                        // 2
        bakeACake()
        cakesBaked++
    } while (cakesBaked < cakesEaten)

}
  • 特に相違なし
  • do-whileは結構避けられている印象なので、実装されているのは意外

Iterators

class Animal(val name: String)

class Zoo(val animals: List<Animal>) {

    operator fun iterator(): Iterator<Animal> {             // 1
        return animals.iterator()                           // 2
    }
}

fun main() {

    val zoo = Zoo(listOf(Animal("zebra"), Animal("lion")))

    for (animal in zoo) {                                   // 3
        println("Watch out, it's a ${animal.name}")
    }

}
  • operator修飾子をつけたiteratorファンクションを定義することで、イテレータを実装できる
netainetai

Ranges

for(i in 0..3) {             // 1
    print(i)
}
print(" ")

for(i in 0 until 3) {        // 2
    print(i)
}
print(" ")

for(i in 2..8 step 2) {      // 3
    print(i)
}
print(" ")

for (i in 3 downTo 0) {      // 4
    print(i)
}
print(" ")

    1. は0123、2. は012が表示される
  • 3.は2から8まで2歩ずつなので、2468
    1. は3から0へ
for (c in 'a'..'d') {        // 1
    print(c)
}
print(" ")

for (c in 'z' downTo 's' step 2) { // 2
    print(c)
}
print(" ")
  • Charのレンジもサポートされている
val x = 2
if (x in 1..5) {            // 1
    print("x is in range from 1 to 5")
}
println()

if (x !in 6..10) {          // 2
    print("x is not in range from 6 to 10")
}
  • if条件に組み込むことも可能
netainetai

Equality Checks

val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Twain", "Shakespeare", "Hemingway")

println(authors == writers)   // 1
println(authors === writers)  // 2
  • =====は挙動が若干異なる
    • ==a.equals(b)
    • ===は参照先が同一かどうか
    1. はtrueとなる(setは順序を持たない)
    1. はfalseとなる(当然、参照先が異なるため)
netainetai

Conditional Expression

fun max(a: Int, b: Int) = if (a > b) a else b         // 1

println(max(99, -42))
  • Kotlinにはcondition ? then : elseのような記法はなく、ifを使う
netainetai

Data Classes

data class User(val name: String, val id: Int) {           // 1
    override fun equals(other: Any?) =
        other is User && other.id == this.id               // 2
}
fun main() {
    val user = User("Alex", 1)
    println(user)                                          // 3

    val secondUser = User("Alex", 1)
    val thirdUser = User("Max", 2)

    println("user == secondUser: ${user == secondUser}")   // 4
    println("user == thirdUser: ${user == thirdUser}")

    // hashCode() function
    println(user.hashCode())                               // 5
    println(secondUser.hashCode())
    println(thirdUser.hashCode())

    // copy() function
    println(user.copy())                                   // 6
    println(user === user.copy())                          // 7
    println(user.copy("Max"))                              // 8
    println(user.copy(id = 3))                             // 9

    println("name = ${user.component1()}")                 // 10
    println("id = ${user.component2()}")
}
  • data classでクラス宣言することで、ボイラープレートを省略できる
  • equals(), toStirng(), hashCode(), copy(), componentN()が推論される
    • toString()User(name=Alex, id=1)の形式
netainetai

Enum Classes

enum class State {
    IDLE, RUNNING, FINISHED                           // 1
}

fun main() {
    val state = State.RUNNING                         // 2
    val message = when (state) {                      // 3
        State.IDLE -> "It's idle"
        State.RUNNING -> "It's running"
        State.FINISHED -> "It's finished"
    }
    println(message)
}
    1. whenのケースが列挙子を網羅できていれば、elseケースを書かなくても良くなる

enum class Color(val rgb: Int) {                      // 1
    RED(0xFF0000),                                    // 2
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00);

    fun containsRed() = (this.rgb and 0xFF0000 != 0)  // 3
}

fun main() {
    val red = Color.RED
    println(red)                                      // 4
    println(red.containsRed())                        // 5
    println(Color.BLUE.containsRed())                 // 6
    println(Color.YELLOW.containsRed())               // 7
}
  • enumは他のクラスのようにプロパティやメソッドを持つことができる
    • その場合列挙子の最後はセミコロンで区切る
    1. "RED"が返る
    1. はYERROWもFFを持つのでtrueが返る
netainetai

Sealed Classes

sealed class Mammal(val name: String)                                                   // 1

class Cat(val catName: String) : Mammal(catName)                                        // 2
class Human(val humanName: String, val job: String) : Mammal(humanName)

fun greetMammal(mammal: Mammal): String {
    when (mammal) {                                                                     // 3
        is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"    // 4
        is Cat -> return "Hello ${mammal.name}"                                         // 5     
    }                                                                                   // 6
}

fun main() {
    println(greetMammal(Cat("Snowy")))
}
  • sealedで同じファイル以外からの継承を封じる
    • Kotlinではopenのように継承許可を明示しないと継承できないが、openでは広すぎる場合に使うらしい
netainetai

Object Keyword

import java.util.Random

class LuckDispatcher {                    //1 
    fun getNumber() {                     //2 
        var objRandom = Random()
        println(objRandom.nextInt(90))
    }
}

fun main() {
    val d1 = LuckDispatcher()             //3
    val d2 = LuckDispatcher()
    
    d1.getNumber()                        //4 
    d2.getNumber()
}
  • 殆どの言語と同じようにKotlinも、classは設計書であり、objectはクラスのインスタンスである(=オブジェクト指向)

  • 加えて、Kotlinはobject keywordを持つ

    • object keywordは単一の実装でデータタイプを取得するために用いられる
      • 単一=シングルトンのこと
    • classではなくobjectを宣言する。classやコンストラクタを書かず、アクセスされたときに一度だけ生成されるインスタンスとなる

Object Expression

fun rentPrice(standardDays: Int, festivityDays: Int, specialDays: Int): Unit {  //1

    val dayRates = object {                                                     //2
        var standard: Int = 30 * standardDays
        var festivity: Int = 50 * festivityDays
        var special: Int = 100 * specialDays
    }

    val total = dayRates.standard + dayRates.festivity + dayRates.special       //3

    print("Total price: $$total")                                               //4

}

fun main() {
    rentPrice(10, 2, 1)                                                         //5
}
  • object Expression はシンプルなオブジェクトおよびプロパティからなる
  • クラス宣言なしで使用できる
  • Javaの匿名クラスに近い実装となる

Object Declaration

object DoAuth {                                                 //1 
    fun takeParams(username: String, password: String) {        //2 
        println("input Auth parameters = $username:$password")
    }
}

fun main(){
    DoAuth.takeParams("foo", "qwerty")                          //3
}

Companion Objects

class BigBen {                                  //1 
    companion object Bonger {                   //2
        fun getBongs(nTimes: Int) {             //3
            for (i in 1 .. nTimes) {
                print("BONG ")
            }
        }
    }
}

fun main() {
    BigBen.getBongs(12)                         //4
}
  • Object宣言をクラス内で行う場合、companion objectを宣言する
  • これはJavaでいうstatic メソッドに近い
    • 「代わりにトップレベル関数を利用しても良い」と書かれている
netainetai

Higher-Order Functions

fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {  // 1
    return operation(x, y)                                          // 2
}

fun sum(x: Int, y: Int) = x + y                                     // 3

fun main() {
    val sumResult = calculate(4, 5, ::sum)                          // 4
    val mulResult = calculate(4, 5) { a, b -> a * b }               // 5
    println("sumResult $sumResult, mulResult $mulResult")
}
    1. calculateファンクションは高階関数であり、xyの他に、opetationとして他の関数を渡せるようになっている
    1. operationに入ったファンクションにxとyを渡して返す
    1. ファンクションを参照する場合::を付与する
    1. ラムダ式を利用する場合

Returning Functions

fun operation(): (Int) -> Int {                                     // 1
    return ::square
}

fun square(x: Int) = x * x                                          // 2

fun main() {
    val func = operation()                                          // 3
    println(func(2))                                                // 4
}
  • ファンクションを返す場合、返り値型はファンクションの引数と返り値に合わせて(Int) -> Intのように記述する
netainetai

Lambda Function

val upperCase1: (String) -> String = { str: String -> str.uppercase() } // 1

val upperCase2: (String) -> String = { str -> str.uppercase() }         // 2

val upperCase3 = { str: String -> str.uppercase() }                     // 3

// val upperCase4 = { str -> str.uppercase() }                          // 4

val upperCase5: (String) -> String = { it.uppercase() }                 // 5

val upperCase6: (String) -> String = String::uppercase                  // 6

println(upperCase1("hello"))
println(upperCase2("hello"))
println(upperCase3("hello"))
println(upperCase5("hello"))
println(upperCase6("hello"))
    1. 省略なしの書き方。(String) -> StringはFunctionの型を指定している
    1. 型宣言から推論する形
    1. 逆に、ファンクション型をlambda式から推論もできる
    1. これは通らない。ファンクション型とラムダ式内の型の両方を省略することはできない
    1. 引数が一つの場合、引数名を省略できる。その場合、引数名の代わりにitを使用できる。itの型は推論される
    1. ラムダ式を呼び出す場合、::を使用する
netainetai

Extension Functions and Properties

data class Item(val name: String, val price: Float)                                         // 1  

data class Order(val items: Collection<Item>)  

fun Order.maxPricedItemValue(): Float = this.items.maxByOrNull { it.price }?.price ?: 0F    // 2  
fun Order.maxPricedItemName() = this.items.maxByOrNull { it.price }?.name ?: "NO_PRODUCTS"

val Order.commaDelimitedItemNames: String                                                   // 3
    get() = items.map { it.name }.joinToString()

fun main() {

    val order = Order(listOf(Item("Bread", 25.0F), Item("Wine", 29.0F), Item("Water", 12.0F)))
    
    println("Max priced item name: ${order.maxPricedItemName()}")                           // 4
    println("Max priced item value: ${order.maxPricedItemValue()}")
    println("Items: ${order.commaDelimitedItemNames}")                                      // 5

}
  • Kotlinはextension(拡張)機能によってクラスに新しいメンバを加えられる

  • 拡張は、ファンクションとプロパティの2種類を加えられる

  • 拡張の特徴は型が必要ないこと

    1. Order型に拡張ファンクションを追加
    1. Order型に拡張プロパティを追加
fun <T> T?.nullSafeToString() = this?.toString() ?: "NULL"  // 1
  • nullチェックにも使える
netainetai

List

val systemUsers: MutableList<Int> = mutableListOf(1, 2, 3)        // 1
val sudoers: List<Int> = systemUsers                              // 2

fun addSystemUser(newUser: Int) {                                 // 3
    systemUsers.add(newUser)                      
}

fun getSysSudoers(): List<Int> {                                  // 4
    return sudoers
}

fun main() {
    addSystemUser(4)                                              // 5 
    println("Tot sudoers: ${getSysSudoers().size}")               // 6
    getSysSudoers().forEach {                                     // 7
        i -> println("Some useful info on user $i")
    }
    // getSysSudoers().add(5) <- Error!                           // 8
}
  • MutableListはその名の通りミュータブル、Listはイミュータブル
  • listOfでリストの作成
  • list.add(a)で追加
    1. はイミュータブルなリストを返す。リストのゲッター
netainetai

Set

val openIssues: MutableSet<String> = mutableSetOf("uniqueDescr1", "uniqueDescr2", "uniqueDescr3") // 1

fun addIssue(uniqueDesc: String): Boolean {                                                       
    return openIssues.add(uniqueDesc)                                                             // 2
}

fun getStatusLog(isAdded: Boolean): String {                                                       
    return if (isAdded) "registered correctly." else "marked as duplicate and rejected."          // 3
}

fun main() {
    val aNewIssue: String = "uniqueDescr4"
    val anIssueAlreadyIn: String = "uniqueDescr2" 

    println("Issue $aNewIssue ${getStatusLog(addIssue(aNewIssue))}")                              // 4
    println("Issue $anIssueAlreadyIn ${getStatusLog(addIssue(anIssueAlreadyIn))}")                // 5 
}
  • setは順序の概念がない。要素の重複を許可しない
  • それ以外は基本的にListと同じ
netainetai

Map

const val POINTS_X_PASS: Int = 15
val EZPassAccounts: MutableMap<Int, Int> = mutableMapOf(1 to 100, 2 to 100, 3 to 100)   // 1
val EZPassReport: Map<Int, Int> = EZPassAccounts                                        // 2

fun updatePointsCredit(accountId: Int) {
    if (EZPassAccounts.containsKey(accountId)) {                                        // 3
        println("Updating $accountId...")                                               
        EZPassAccounts[accountId] = EZPassAccounts.getValue(accountId) + POINTS_X_PASS  // 4
    } else {
        println("Error: Trying to update a non-existing account (id: $accountId)")
    } 
}

fun accountsReport() {
    println("EZ-Pass report:")
    EZPassReport.forEach {                                                              // 5
        k, v -> println("ID $k: credit $v")
    }
}

fun main() {
    accountsReport()                                                                    // 6
    updatePointsCredit(1)                                                               // 7
    updatePointsCredit(1)                                                               
    updatePointsCredit(5)                                                               // 8 
    accountsReport()                                                                    // 9
}
  • Mapはキーと値からなる
  • infixファンクションのtoは、Mapをきれいに作るのに使える
netainetai

Filter

val numbers = listOf(1, -2, 3, -4, 5, -6)      // 1

val positives = numbers.filter { x -> x > 0 }  // 2

val negatives = numbers.filter { it < 0 }      // 3
  • コレクションをフィルタリングできる
  • 引数はラムダ式
netainetai

map

val numbers = listOf(1, -2, 3, -4, 5, -6)     // 1

val doubled = numbers.map { x -> x * 2 }      // 2

val tripled = numbers.map { it * 3 }          // 3
  • コレクション全体にラムダ式引数を適用できる
netainetai

any, all, none

Function any

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val anyNegative = numbers.any { it < 0 }             // true

val anyGT6 = numbers.any { it > 6 }                  // false
  • 一つでも条件に当てはまるかどうか

Function all

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val allEven = numbers.all { it % 2 == 0 }            // false

val allLess6 = numbers.all { it < 6 }                // true
  • すべて当てはまるかどうか

Function none

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1

val allEven = numbers.none { it % 2 == 1 }           // false

val allLess6 = numbers.none { it > 6 }               // true
  • どれも当てはまらないかどうか
  • これはちょっと使いづらそう?
netainetai

find, findLast

val words = listOf("Lets", "find", "something", "in", "collection", "somehow")  // 1

val first = words.find { it.startsWith("some") }                                // something
val last = words.findLast { it.startsWith("some") }                             // somehow

val nothing = words.find { it.contains("nothing") }                             // null
  • findは前から、findLastは後ろから検索し、ヒットしたらその文字を返す
  • ヒットしない場合はnullを返す
netainetai

first, last

val numbers = listOf(1, -2, 3, -4, 5, -6)            // 1val first = numbers.first()                          // 2
val last = numbers.last()                            // 3val firstEven = numbers.first { it % 2 == 0 }        // 4
val lastOdd = numbers.last { it % 2 != 0 }           // 5
  • firstは最初、lastは最後の要素取り出し
    • ちょうど最近「Javaでlastの取り出し面倒だな」と思っていたのでありがたい機能
  • 式を入れると条件に沿った最初(最後)の要素

firstOrNull, lastOrNull

val words = listOf("foo", "bar", "baz", "faz")         // 1
val empty = emptyList<String>()                        // 2

val first = empty.firstOrNull()                        // null
val last = empty.lastOrNull()                          // null

val firstF = words.firstOrNull { it.startsWith('f') }  // foo
val firstZ = words.firstOrNull { it.startsWith('z') }  // null
val lastF = words.lastOrNull { it.endsWith('f') }      // null
val lastZ = words.lastOrNull { it.endsWith('z') }      // fuz
  • マッチングしない場合にnullを返す
netainetai

count

val numbers = listOf(1, -2, 3, -4, 5, -6)

val totalCount = numbers.count()                     // 6
val evenCount = numbers.count { it % 2 == 0 }        // 3
  • そのままだとsize
netainetai

associateBy, groupBy

data class Person(val name: String, val city: String, val phone: String) // 1

val people = listOf(                                                     // 2
    Person("John", "Boston", "+1-888-123456"),
    Person("Sarah", "Munich", "+49-777-789123"),
    Person("Svyatoslav", "Saint-Petersburg", "+7-999-456789"),
    Person("Vasilisa", "Saint-Petersburg", "+7-999-123456"))

val phoneBook = people.associateBy { it.phone }                          // 3
val cityBook = people.associateBy(Person::phone, Person::city)           // 4
val peopleCities = people.groupBy(Person::city, Person::name)            // 5
val lastPersonCity = people.associateBy(Person::city, Person::name)      // 6
  • クラスの特定の値をSQLっぽく取り出せる
    1. {+1-888-123456=Person(name=John, city=Boston, phone=+1-888-123456), +49-777-789123=Person(name=Sarah, city=Munich, phone=+49-777-789123), +7-999-456789=Person(name=Svyatoslav, city=Saint-Petersburg, phone=+7-999-456789), +7-999-123456=Person(name=Vasilisa, city=Saint-Petersburg, phone=+7-999-123456)}
    1. {+1-888-123456=Boston, +49-777-789123=Munich, +7-999-456789=Saint-Petersburg, +7-999-123456=Saint-Petersburg}
    1. {Boston=[John], Munich=[Sarah], Saint-Petersburg=[Svyatoslav, Vasilisa]}
    1. {Boston=John, Munich=Sarah, Saint-Petersburg=Vasilisa}
netainetai

partition

val numbers = listOf(1, -2, 3, -4, 5, -6)                // 1

val evenOdd = numbers.partition { it % 2 == 0 }           // 2
val (positives, negatives) = numbers.partition { it > 0 } // 3
  • 条件に当てはまるものを取り出す
    1. のように変数を2つ取ることで、trueのものを前者に、falseのものを後者に詰め込める
netainetai

flatMap

val fruitsBag = listOf("apple","orange","banana","grapes")  // 1
val clothesBag = listOf("shirts","pants","jeans")           // 2
val cart = listOf(fruitsBag, clothesBag)                    // 3
val mapBag = cart.map { it }                                // 4
val flatMapBag = cart.flatMap { it }                        // 5
  • mapBagの中身は[[apple, orange, banana, grapes], [shirts, pants, jeans]]
  • flatMapBagは一列に並べるので、[apple, orange, banana, grapes, shirts, pants, jeans]になる
netainetai

minOrNull, maxOrNull

val numbers = listOf(1, 2, 3)
val empty = emptyList<Int>()
val only = listOf(3)

println("Numbers: $numbers, min = ${numbers.minOrNull()} max = ${numbers.maxOrNull()}") // 1
println("Empty: $empty, min = ${empty.minOrNull()}, max = ${empty.maxOrNull()}")        // 2
println("Only: $only, min = ${only.minOrNull()}, max = ${only.maxOrNull()}")            // 3
  • 最大/最小かnullを返す
netainetai

sorted

val shuffled = listOf(5, 4, 2, 1, 3, -10)                   // 1
val natural = shuffled.sorted()                             // 2
val inverted = shuffled.sortedBy { -it }                    // 3
val descending = shuffled.sortedDescending()                // 4
val descendingBy = shuffled.sortedByDescending { abs(it)  } // 5
  • リストのソート
  • sortedBy(){it}: it-absをつけて序列を自然順から変更できる
  • 逆順はsortedDescending()
netainetai

Map Element Access

val map = mapOf("key" to 42)

val value1 = map["key"]                                     // 1
val value2 = map["key2"]                                    // 2

val value3: Int = map.getValue("key")                       // 1

val mapWithDefault = map.withDefault { k -> k.length }
val value4 = mapWithDefault.getValue("key2")                // 3

try {
    map.getValue("anotherKey")                              // 4
} catch (e: NoSuchElementException) {
    println("Message: $e")
}
    1. mapへのアクセスはmap[key]の形。存在しない場合nullを返す
  • よって 2. の場合nullとなる
  • map.getBalue(key)の場合、存在しない場合は例外を返す
  • map.withDefault{}で作られたmapはgetValueに対して例外ではなくデフォルト値を返すようになる
  • よって3. は"key2"のlengthである4を返す
netainetai

zip

val A = listOf("a", "b", "c")                  // 1
val B = listOf(1, 2, 3, 4)                     // 1

val resultPairs = A zip B                      // 2
val resultReduce = A.zip(B) { a, b -> "$a$b" } // 3
  • A zip Bは[(a, 1), (b, 2), (c, 3)]
  • A.zip(B) { a, b -> "$a$b" }は[a1, b2, c3]
netainetai

getOrElse

val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 })    // 1
println(list.getOrElse(10) { 42 })   // 2
  • インデックス外の場合は42を返す
val map = mutableMapOf<String, Int?>()
println(map.getOrElse("x") { 1 })       // 1

map["x"] = 3
println(map.getOrElse("x") { 1 })       // 2

map["x"] = null
println(map.getOrElse("x") { 1 })       // 3
  • 上から1, 3, 1が返る
netainetai

Delegation Pattern

Delegation(委譲)というデザインパターンを実装しやすくする機能
byというInfixを使うことで、煩雑なボイラープレートを排除することができる

  • 委譲とはあるオブジェクトの操作を一部他のオブジェクトに代替させる手法である
  • 委譲ではあるオブジェクトの操作を他のオブジェクトに代替させるため冗長な記述が増えてしまいがち
  • だが Kotlinの by を利用することで冗長な記述をせず、簡単に操作を委譲させることができる。
    [Kotlin]by で 委譲する仕組みを理解する

同記事でここも重要か

というように by を利用すると インタフェースで実装すべき操作を他のオブジェクトへ簡単に委譲できます。
次のように 2つのインタフェースに対して処理を委譲することも可能になっているので、多くのメソッド・プロパティを持つ複数のインタフェースの処理を委譲していときはすごく便利です。

interface SoundBehavior {                                                          // 1
    fun makeSound()
}

class ScreamBehavior(val n:String): SoundBehavior {                                // 2
    override fun makeSound() = println("${n.toUpperCase()} !!!")
}

class RockAndRollBehavior(val n:String): SoundBehavior {                           // 2
    override fun makeSound() = println("I'm The King of Rock 'N' Roll: $n")
}

// Tom Araya is the "singer" of Slayer
class TomAraya(n:String): SoundBehavior by ScreamBehavior(n)                       // 3

// You should know ;)
class ElvisPresley(n:String): SoundBehavior by RockAndRollBehavior(n)              // 3

fun main() {
    val tomAraya = TomAraya("Thrash Metal")
    tomAraya.makeSound()                                                           // 4
    val elvisPresley = ElvisPresley("Dancin' to the Jailhouse Rock.")
    elvisPresley.makeSound()
}
  • "THRASH METAL !!!"と"I'm The King of Rock 'N' Roll: Dancin' to the Jailhouse Rock."が出力される
netainetai

Delegated Properties

委譲プロパティ

delegate property とは名前の通り、「委譲されたプロパティ」です。
もう少しわかり易い言葉で説明すると、変数のゲッター・セッターの処理を本来とは別のクラスに任せる事ができるわけです。
処理が委譲できるとボイラーテンプレートや遅延評価などが可能になります。
Kotlin Delegate Property について調べてみた

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()                                               // 1

    override fun toString() = "Example Class"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {        // 2     
        return "$thisRef, thank you for delegating '${prop.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) { // 2
        println("$value has been assigned to ${prop.name} in $thisRef")
    }
}

fun main() {
    val e = Example()
    println(e.p)
    e.p = "NEW"
}
    1. by infitで下のDelegateクラスを指定
    1. thisRefにはExample@736e9adb(参照先?)が入っているが、これが#toString()でExample Classに上書きされるので、println()の出力はExample Class, thank you for delegating 'p' to me!になる(ここがいまいち分からない)
netainetai

Named Arguments

println(format("mario", "example.com"))                         // 1
println(format("domain.com", "username"))                       // 2
println(format(userName = "foo", domain = "bar.com"))           // 3
println(format(domain = "frog.com", userName = "pepe"))         // 4
  • 上でも説明のあったNamed Argument
  • #format(userName, domain)は、userNamedomain@で結合する関数
  • 2が期待と異なるdomain.com@usernameを出力する一方で、4はNamed Argumentsを使用して期待通りのpepe@frog.comが出力される
netainetai

String Templates

val greeting = "Kotliner"

println("Hello $greeting")                  // 1 
println("Hello ${greeting.toUpperCase()}")  // 2
  • $を付与することでString内に変数を組み込める
  • ${}で{}内にexpressionを書ける
netainetai

Destructuring Declarations

Destructionという単語は「破壊」のイメージだが、Destructuring Declarations分割宣言とか分解宣言のほうが近いはず

val (x, y, z) = arrayOf(5, 10, 15)                              // 1

val map = mapOf("Alice" to 21, "Bob" to 25)
for ((name, age) in map) {                                      // 2
    println("$name is $age years old")          
}

val (min, max) = findMinMax(listOf(100, 90, 50, 98, 76, 83))    // 3
    1. がわかりやすい。このように()で複数の変数名を囲んで配列を代入すると、それぞれに代入される
    • 変数名が不足している場合は通る((x, y)とすると、5と10が入る
    • 変数名が超過している場合、ArrayIndexOutOfBoundsException
  • mapは2. のように展開できる
  • findMinMax()はPair型を返すので、このように受け取れる
netainetai

Smart Casts

  • nullable型からnon-null型へのキャスト
  • 継承元から継承先への型キャスト
    が簡単にできる
val date: ChronoLocalDate? = LocalDate.now()    // 1

if (date != null) {
    println(date.isLeapYear)                    // 2
}

if (date != null && date.isLeapYear) {          // 3
    println("It's a leap year!")
}

if (date == null || !date.isLeapYear) {         // 4
    println("There's no Feb 29 this year...")
}

if (date is LocalDate) {
    val month = date.monthValue                 // 5
    println(month)
}
        1. nullable型で使えないisLeapYear(閏年判定)が直前のnull判定を経て使えるようになっている
  • 継承元(ChronoLocalDate)から継承先(LocalDate)へのキャスト