Kotlin Examplesの学習メモ
教材
Kotlin Examples
以下、コードはKotlin Examplesから引用したものを使用する
速習 Kotlin
環境
Kotlin Playground
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の間の子感がある(自分がそれらしか知らないだけで、もっと似てる言語があると思うが……)
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)
- 返り値は型推論される
- 引数名を明示することで、引数の順番に縛られず記述できる
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
と出力されるようになる - (恐らく)自然言語風に読めるようになる、可読性が上がるのがメリット
- 2で
- 3は標準ライブラリの
to
を使用しており、(Ferrari, Katrina)
のPair型が出力される - 4はtoを手動で実装した場合の書き方
- 5, 6はPerson型のクラスに
like
infixを実装した場合。A like Bと書くだけで、likedPeople
にBがaddされる
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で切り出すように[]
をオーバーロードしたということ
vararg
Parameters
Functions with 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
- *を付けることで、配列の内容を渡すことができるようになる
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型として通る
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のコードが通るようになる) - 推論型(
var
やval
)もnullを許容しない(6はコンパイルエラー) - 7はnon-null Stringをパラメータとしたファンクションのため、9はコンパイルエラーとなる
- じゃあどうすればいいのさ、は次で
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を代入するとぬるぽする(当然)
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
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>が省略されている
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()
}
-
-
open
はクラス継承(ファンクションならオーバライド)可能の明示
- nullableの
?
と同様に、明示しないと継承/オーバライドできない
-
-
- 継承はクラスに型宣言を行うような書き方。extendsではない
- 空の括弧はスーパークラスのデフォルトコンストラクタを示す
つまり、コンストラクタの渡し方は以下の形になる
open class Dog(name: String){//処理}
class Yorkshire(name: String) : Dog(name){//処理}
-
- オーバライドする場合、
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"のコンストラクタと解釈される
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文が入っており、その中に分岐先を用意する
-
- や 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)が必須(コンパイルエラー)
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ファンクションを定義することで、イテレータを実装できる
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(" ")
-
- は0123、2. は012が表示される
- 3.は2から8まで2歩ずつなので、2468
-
- は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条件に組み込むことも可能
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)
-
===
は参照先が同一かどうか
-
-
- はtrueとなる(
set
は順序を持たない)
- はtrueとなる(
-
- はfalseとなる(当然、参照先が異なるため)
Conditional Expression
fun max(a: Int, b: Int) = if (a > b) a else b // 1
println(max(99, -42))
- Kotlinには
condition ? then : else
のような記法はなく、ifを使う
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)
の形式
-
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)
}
-
-
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
は他のクラスのようにプロパティやメソッドを持つことができる- その場合列挙子の最後はセミコロンで区切る
-
- は
"RED"
が返る
- は
-
- はYERROWもFFを持つのでtrueが返る
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では広すぎる場合に使うらしい
- Kotlinでは
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 メソッドに近い
- 「代わりにトップレベル関数を利用しても良い」と書かれている
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")
}
-
- calculateファンクションは高階関数であり、
x
とy
の他に、opetation
として他の関数を渡せるようになっている
- calculateファンクションは高階関数であり、
-
- operationに入ったファンクションにxとyを渡して返す
-
- ファンクションを参照する場合
::
を付与する
- ファンクションを参照する場合
-
- ラムダ式を利用する場合
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
のように記述する
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"))
-
- 省略なしの書き方。
(String) -> String
はFunctionの型を指定している
- 省略なしの書き方。
-
- 型宣言から推論する形
-
- 逆に、ファンクション型をlambda式から推論もできる
-
- これは通らない。ファンクション型とラムダ式内の型の両方を省略することはできない
-
- 引数が一つの場合、引数名を省略できる。その場合、引数名の代わりにitを使用できる。itの型は推論される
-
- ラムダ式を呼び出す場合、
::
を使用する
- ラムダ式を呼び出す場合、
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種類を加えられる
-
拡張の特徴は型が必要ないこと
-
-
Order
型に拡張ファンクションを追加
-
-
-
Order
型に拡張プロパティを追加
-
fun <T> T?.nullSafeToString() = this?.toString() ?: "NULL" // 1
- nullチェックにも使える
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)
で追加 -
- はイミュータブルなリストを返す。リストのゲッター
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
と同じ
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をきれいに作るのに使える
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
- コレクションをフィルタリングできる
- 引数はラムダ式
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
- コレクション全体にラムダ式引数を適用できる
any, all, none
any
Function val numbers = listOf(1, -2, 3, -4, 5, -6) // 1
val anyNegative = numbers.any { it < 0 } // true
val anyGT6 = numbers.any { it > 6 } // false
- 一つでも条件に当てはまるかどうか
all
Function 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
- すべて当てはまるかどうか
none
Function 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
- どれも当てはまらないかどうか
- これはちょっと使いづらそう?
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
を返す
first, last
val numbers = listOf(1, -2, 3, -4, 5, -6) // 1
val first = numbers.first() // 2
val last = numbers.last() // 3
val 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を返す
count
val numbers = listOf(1, -2, 3, -4, 5, -6)
val totalCount = numbers.count() // 6
val evenCount = numbers.count { it % 2 == 0 } // 3
- そのままだとsize
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-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-888-123456=Boston, +49-777-789123=Munich, +7-999-456789=Saint-Petersburg, +7-999-123456=Saint-Petersburg}
-
- {Boston=[John], Munich=[Sarah], Saint-Petersburg=[Svyatoslav, Vasilisa]}
-
- {Boston=John, Munich=Sarah, Saint-Petersburg=Vasilisa}
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
- 条件に当てはまるものを取り出す
-
- のように変数を2つ取ることで、trueのものを前者に、falseのものを後者に詰め込める
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]になる
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を返す
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()
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")
}
-
- mapへのアクセスは
map[key]
の形。存在しない場合nullを返す
- mapへのアクセスは
- よって 2. の場合nullとなる
-
map.getBalue(key)
の場合、存在しない場合は例外を返す - map.withDefault{}で作られたmapはgetValueに対して例外ではなくデフォルト値を返すようになる
- よって3. は"key2"のlengthである4を返す
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]
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が返る
スコープ関数(let, run, with, apply, also)
ドキュメントでは理解が追いつかなかった
こちらが詳しい
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."が出力される
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"
}
-
-
by
infitで下のDelegateクラスを指定
-
-
- thisRefには
Example@736e9adb
(参照先?)が入っているが、これが#toString()でExample Class
に上書きされるので、println()の出力はExample Class, thank you for delegating 'p' to me!
になる(ここがいまいち分からない)
- thisRefには
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)
は、userName
とdomain
を@
で結合する関数 - 2が期待と異なる
domain.com@username
を出力する一方で、4はNamed Argumentsを使用して期待通りのpepe@frog.com
が出力される
String Templates
val greeting = "Kotliner"
println("Hello $greeting") // 1
println("Hello ${greeting.toUpperCase()}") // 2
-
$
を付与することでString内に変数を組み込める -
${}
で{}内にexpressionを書ける
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
-
- がわかりやすい。このように
()
で複数の変数名を囲んで配列を代入すると、それぞれに代入される
- 変数名が不足している場合は通る(
(x, y)
とすると、5と10が入る - 変数名が超過している場合、ArrayIndexOutOfBoundsException
- がわかりやすい。このように
- mapは2. のように展開できる
-
findMinMax()
はPair型を返すので、このように受け取れる
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)
}
-
-
-
- nullable型で使えない
isLeapYear
(閏年判定)が直前のnull判定を経て使えるようになっている
- nullable型で使えない
-
-
- 継承元(ChronoLocalDate)から継承先(LocalDate)へのキャスト