Kotlin

Kotlin Webアプリケーション 勉強メモ

Welcome to ようこそ Kotlin world

特徴

  • 簡素な記述
    • 型推論
    • データクラス
    • ラムダ式
    • 高階関数
  • 安全
    • nullの扱いに厳格
      • 不適切に記述するとコンパイルエラー
      • null pointer exceptionが出ない

開発環境構築と最初のプログラム

Try Kotlin

http://try.kotl.in
WebブラウザでKotlinを実行して動作を確認することができる

CLIコンパイラ

https://kotlinlang.org/docs/tutorials/command-line.html

以下のコマンドを実行

# SDKMAN!のインストール
curl -s https://get.sdkman.io | bash

## SDKMAN!のセットアップ
source "/Users/masahiko/.sdkman/bin/sdkman-init.sh"

# Kotlinのインストール
sdk install kotlin

# homebrewでもインストールできる
brew update
brew install kotlin

コンパイルと実行

以下のコマンドでKotlinソースのコンパイル

kotlinc filename.kt -include-runtime -d filename.jar

実行(Javaと一緒)

java -jar filename.jar

対話型評価環境

ソースファイルを指定しないでkotlincを実行すると対話型評価環境(REPL)モードで起動する

[2017-10-11 23:20:02] hogehoge.local:~/work/dev/kotlin_webapp
> kotlinc
Welcome to Kotlin version 1.1.51 (JRE 1.8.0_45-b14)
Type :help for help, :quit for quit
>>> "Kotlin".reversed()
niltoK
>>> "Kotlin".reversed()
niltoK
>>> "Kotlin".count()
6
>>> "Kotlin".toUpperCase()
KOTLIN
>>> :quit

IntelliJ IDEA

インストール

https://www.jetbrains.com/idea/download/

  • Ultimate: 有償版
  • Community: 無償版

CommunityダウンロードしてDnDすれば完了。
Ultimateはお金あったら買おう。

JDKの設定

  • Configre > Project Defaults > Project Structure
  • NewからJDKのホームディレクトリを洗濯

Kotlinの最新化

  • Configre > Plugins
  • Kotlinを検索
  • Kotlinが最新版じゃなかったらUpdateボタンが表示される

Hello worldの実行

HelloWorld.ktを作成

HelloWorld.kt
package sample

fun main(args: Array<String>) {
    println("Hello, world!")
}

ファイルを右クリックして「Run」を実行
以下の警告が出るけど無視していいっぽい

objc[20394]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/bin/java (0x10138b4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1023de4e0). One of the two will be used. Which one is undefined.

参考: https://qiita.com/akatsuki174/items/66e3e18c1cdd56640483#%E8%AD%A6%E5%91%8A%E5%AF%BE%E5%87%A6

基本構文

基本データ型とリテラル

数値

種類 ビット幅 リテラル例
Double 浮動小数点 64 123.4 123.4e5
Float 浮動小数点 32 123.4 123.4e5
Long 整数 64 1234 1234L
Int 整数 32 1234 0xAB 0b1001
Short 整数 16 1234 0xAB 0b1001
Byte 整数 8 123 0x0F 0b1001

その他の基本型

種類 リテラル例
Boolean 真偽値 true false
Char 文字 'a' '0' '\u592a'
String 文字列 "こんにちは\n" ”””I am happy!”””

変数

// 再代入不可
val name = "marina"
name = "maria" // コンパイルエラー

// 最大入可
var age = 32
age = 33 // コンパイルエラーは出ない

// 型を明示することができる
// 上記のように明示しない場合は型推論でリテラルに結びついた型設定される
val name: String = "marina"

よく使うオブジェクト

文字列

// +演算子
val str = +で” + ”文字列が” + ”連結できる”

// raw string (複数行の文字列を表現)
val message = """I
love
Kotlin.
"""

// |以前の文字が削除される
val message = """I
                |love
                |Kotlin.""".trimMargin()

配列

// Arrayクラス
val srts: Array<String> = arrayOf("foo", "bar", "baz")

// プリミティブ型の配列
// Arrayクラスを使うより高速
val ints: IntArray = intArrayOf(1, 2, 3)

リスト

順序付きコレクション

// リストの定義
val ints: List<Int> = listOf(1, 2, 3)
ints[2] = 4 // Listはイミュータブルなのでコンパイルエラー

// ミュータブルリスト
val ints: MutableList<Int> = mutableListOf(1, 2, 3)
ints[2] = 4 // OK

// add, remove
val a: MutableList<Int> = mutableListOf(1, 2, 3)
val b: List<Int> = a

// aに要素を加えるとbにも影響を与える
a.add(4)

// ダウンキャストして要素追加もできる(MutableListはListのサブタイプ)
(b as MutableList<Int>).add(5) 

// 削除(キー/インデックスじゃなく値を設定する)
a.remove(2)

マップ

キー/バリューのペアを保持するコレクション

// 読み取り専用
val a: Map<Int, String> = mapOf(1 to "one", 2 to "two")

// 変更可能
val b: MutableMap<Int, String> = mutableMapOf(1 to "one", 2 to "two")
b[3] = "three"

レンジ

// 1から10のレンジを生成
1..10

// x in y..z でxがy ~ zに含まれるかどうか確認
5 in 1..10
> true
5 in 6..10
> false

// toListメソッドでレンジをリスト化
(1..5).toList() 
> [1, 2, 3, 4, 5]

// downToで減少方向へのリスト作成
(5 downTo 1).toList()
> [5, 4, 3, 2, 1]

// stepで刻む値を設定可能
(1..10 step 2).toList()
> [1, 3, 5, 7, 9]

(10 downTo 1 step 2).toList()
> [10, 8, 6, 4, 2]

条件分岐

if式

Kotlinは三項演算子が無いので三項演算子的に書くには以下のように記述する
無論普通のif文も書ける

val result = if (true) "TRUE" else "FALSE"
println(result)
> TRUE

when式

Javaでいうswitch文

// 式だから値を返すよ
val result = when(3) {
  1 -> "one"
  2 -> "two"
  3 -> "three"
  else -> "unknown"
}
println(result)
> three

// レンジが使えるよ
val result = when(2) {
  1 -> "one"
  2..5 -> "two..five"
  else -> "unknown"
}
println(result)
> two..five

// 型判定もできるよ
val target: Any = 2
val result = when(target) {
  is Double -> "Double"
  is Int -> "Int"
  else -> "unknown"
}
println(result)
> Int

// こういう書き方もできる
val score = 70
val grade = when {
  90 <= score -> 'A'
  80 <= score -> 'B'
  70 <= score -> 'C'
  60 <= score -> 'D'
  else        -> 'E'
}
println(grade)
> C

ループ

おなじみのwhileとforがある
forはJavaでいう拡張for文に似ており、配列、リストレンジなどのイテレータオブジェクトであればforでループを回せる

// while
var count = 3
while (0 < count--) {
  println("Marina")
}
> Marina
> Marina
> Marina

// do-while
do {
  println("Marina")
  val repeat = Math.random() < 0.5
} while (repeat)

// for
for (i in listOf(1, 3, 5)) {
  println("i = $i")
}
> i = 1
> i = 3
> i = 5

for (i in 5 downTo 1 step 2) {
  println("i = $i")
}
> i = 5
> i = 3
> i = 1

関数

関数の定義と使い方

単文の関数

// 関数の定義
fun double(n: Int): Int = n * 2

fun main(args: Array<String>) {
  val got = double(123)
  println(got) // 「246」と表示される
}

複数の文の関数

fun fizzBuzz(n: Int): String = when {
  n % 15 == 0 -> "FizzBuzz"
  n % 5 == 0  -> "Fizz"
  n % 3 == 0  -> "Buzz"
  else        -> "$n"
}

戻り値が無い場合はオブジェクトUnitを返すようにする(ただし省略できる)

// 戻り値がない場合は明示的にUnitを返すようにする
fun hello(name: String): Unit {
  println("Hello, $name!")
  return Unit
}

// ただし省略できる(省略できるんかーい)
fun hello(name: String) {
  println("Hello, $name!")
}

名前付き引数とデフォルト引数

関数を呼び出す際は通常(Java、PHPとか)引数の順番を意識する必要があるが引数の名前と値をセットで渡すことで順序を気にしなくて良くなる。
※ 名前付き引数(named argment)と呼ぶ

fun foo(a: Char, b: Char) {
  println("1: $a, 2: $b")
}

foo(b = 'Y', a = 'X')

引数のデフォルト値を設定することができる(これはPHPでもある)

fun hello(name: String = "world") {
  println("Hello, $name!")
}

hello()
> Hello, world!

関数オブジェクト

  • Kotlinは関数をオブジェクトとして扱うことができる
    • 関数名の前に「::」を置くことで関数オブジェクトを取得できる
  • また関数の中で関数を定義することができる
    • ローカル関数(local function)
fun succ(n: Int): Int = n + 1

fun main(args: Array<String>) {
    val myFunction = ::succ
    val got = myFunction(5)
    println(got)
}

関数オブジェクトにも方は存在する。
型を明示して記述すると以下のようになる。

val myFunction: (int) -> Int = ::succ

高階関数(higher-order function)

引数として関数を受け取ったり戻り値として関数を返すような関数のことを高階関数(higher-order function)と呼ぶ

fun succ(n: Int): Int = n + 1

fun twice(n: Int, f: (Int) -> Int): Int = f(f(n))

fun main(args: Array<String>) {
  val got = twice(5, ::succ)
  println(got) // 「7」と出力される
}

高階関数のメリット

高階関数を使わない例

// 「5」より大きい数が含まれていたらtrueを返す
fun containsGreaterThen5(ints: List<Int>): Boolean {
  for (i in ints) {
    if (5 < i) return true
  }
  return false
}

// 「3の倍数」が含まれていたらtrueを返す
fun containsMultipleOf3(ints: List<Int>): Boolena {
  for (i in ints) {
    if (i % 3 == 0) return true
  }
  return false
}

fun main(args: Array<Int>) {
  println(containsGreaterThen5(listOf(2, 4, 6))) // 「true」と出力される
  println(containsMultipleOf3(listOf(7, 5, 4))) // 「false」と出力される
}

※ containsGreaterThen5とcontainsMultipleOf3はifの条件式以外は同じロジック

高階関数を使う例

fun contains(ints: List<Int>, predicate: (Int) -> Boolean): Boolean {
  for (i in ints) {
    if (predicate(i)) return true
  }
  return false
}

fun isGreaterThan5(n: Int): Boolean = 5 < n
fun isMultipleOf3(n: Int): Boolean = n % 3 == 0

fun main(args: Array<String>) {
  println(contains(listOf(2, 4, 6), ::isGreaterThan5)) // 「true」と出力される
  println(contains(listOf(7, 5, 4), ::isMultipleOf3)) // 「true」と出力される
}

関数リテラル

  • 関数定義ではなく、必要なときに関数オブジェクトを生成することができる
  • それを関数リテラル(function literal)と呼ぶ
  • JSの const func = () => {} と同じ

文法

{引数1, 引数2, ... ->
  文...
  戻り値
}

// 基本的な書き方
val isGreaterThan5: (Int) -> Boolean = { n: Int -> 5 < n }

// 引数の型は省略できる
val isGreaterThan5: (Int) -> Boolean = { n -> 5 < n }

// 暗黙の引数「it」を使用して引数を省略
val isGreaterThan5: (Int) -> Boolean = { 5 < it }

ラムダ式を高階関数の引数として渡すときに使用できる特別な記法がある。
「最後」の引数が関数を取る場合にのみ、ラムダ式を引数リストの外に出して記述しても良い。

contains(listOf(2, 4, 6) { 5 < it })
↓↓↓
contains(listOf(2, 4, 6)) { 5 < it }

※ 謎機能・・・

無名関数

// 無名関数バージョン
fun main(args: Array<String>) {
  println(contains(listOf(2, 4, 6),
    fun(n: Int): Boolean { return 5 < n }))
  println(contains(listOf(7, 5, 4),
    fun(n: Int): Boolean { return n % 3 == 0 }))
}

// 無名関数 省略バージョン
fun main(args: Array<String>) {
  println(contains(listOf(2, 4, 6),
    fun(n) = 5 < n))
  println(contains(listOf(7, 5, 4),
    fun(n) = n % 3 == 0 }))
}

その他の話題

再帰関数とTCO

再帰関数(関数が定義の中で自分自身を呼び出す)
TCO(Tail Recursive Call, 末尾呼び出し最適化)

// 再帰関数(こちらの書き方では非常に大きい要素数の整数リストを引数に渡すとStackOverflowErrorをスローする)
fun sum(ints: List<Int>): Int =
  if (ints.isEmpty()) 0
  else ints.first() + sum(ints.drop(1))

// TCO(合計値を蓄積するために引数accを追加している)
tailrec fun sum(ints: List<Int>, acc: Int): Int =
  if (ints.isEmpty()) acc
  else sum(ints.drop(1), acc + ints.first())

// ローカル関数に末尾呼び出しをさせる(accに予期しない値を入れられないために)
fun sum(ints: List<Int>): Int {
  tailrec fun go(ints: List<Int>, acc: Int): Int =
    if (ints.isEmpty()) acc
    else go(ints.drop(1)), acc + ints.first())
  return go(ints, 0)
}

可変長引数

引数が異なれば同じ名前の関数を複数定義することは可能(overload)。
ただ、引数の数が異なる関数をたくさん作るのも大変。
そういうときは可変長引数(variable number of arguments)を使う。

for max(n: Int, vararg ints: Int): Int {
  var max = n
  for (i in ints) {
    if (max < i) {
      max = i
    }
  }
  return max
}

max(1, 3, 2)
> 3

max(3, 4, 5, 6, 7, 2)
> 7

max(1, *intArrayOf(2, 3, 4)) // 可変長引数に配列を指定することができる(その場合は先頭に「*」を置く)
> 4

クラスとインターフェースとそのメンバ

Javaと同じくクラス定義をすることができる(あたりまえだね)
classキーワードでクラスを定義できる。インスタンス化にはnewは必要ない。

プロパティとメソッドの宣言

// プロパティの宣言
class Person {
  var name: String = ""
}

val taro: Person = Person()
taro.name = "Taro"

// プライマリコンストラクタ
class Person(n: String) {
  val name: String = n
}

class Person(val name: String) // プライマリコンストラクタの引数でプロパティを定義することができる

// メソッドの宣言
class Person(val name: String) {
  fun show() {
    println("Person(name=$name)")
  }
}

val taro: Person = Person("Taro")
taro.show()
> Person(name=Taro)

継承とオーバーライド

クラス継承の機能もあるよ(あたりまえだねー)
継承可能なクラスにはopenを付ける必要がある。(ココはJavaやPHPと異なる)
openをメソッドの前につけるとオーバーライドが可能になる。
overrideするメソッドは先頭にoverrideをつける必要がある。

// super class
open class Person(val name: String) {
  open fun show() {
    println("Person(name=$name)")
  }
}

// sub class
class Student(val id: Long, name: String): Person(name) {
  override fun show() {
    println("Student(id=${id}, name=${name})")
  }
}

val student: Student = Student(123, "Jiro")
student.show()
> Student(id=123, name=Jiro)


クラスを継承すると型も継承する。
スーパクラスの型をスーパタイプ(supertype)、サブクラスの型をサブタイプ(subtype)と呼ぶ。
上記で例えると型Studentは型Personのサブタイプであるとみなすことができる。つまり型Personのオブジェクトとみなすこともできる。
※ ポリモーフィズム

プロパティ

プロパティはオブジェクトの持つデータ、状態、属性である。
KotlinのプロパティはJavaのフィールドと異なる。プロパティはいわば「アクセスを担うメソッド」と場合によっては「フィールド」を併せ持つ存在。

Personクラス再掲

class Person(var name: String)
val hanako = Person("Hanako")

プロパティnameは自動的に生成される内部的なフィールドと対応している。
このようなフィールドをKotlinではバッキングフィールド(backing field)と呼ぶ。

nameはバッキングフィールドに新しい値をセットする方法とバッキングフィールドの値を取得する方法を提供する。
これによりhanako.name = "Hanako"のような記法でnameに対応するバッキングフィールドに値を保存できる。
また、hoge = hanako.nameで値を取得できる。

get, set方法はカスタマイズできる。

class Person(name: String) {
  val name: String = name
    get() { return field }
    set(value) { return = value }
  val initial: Char
    get() = name[0]
}

上記においてinitialはプロパティnameの最初の文字を取得してそれを取得するgetを定義している。
そのためinitial自体が直接保持するデータがないので、バッキングフィールドは不要であり自動生成されない。

プロパティはそのオブジェクトが持つデータを表現する機能だが、内部的な事情(バッキングフィールドを持っているか、それとも計算式から導出しているかなど)とアクセス方法が別れる。

抽象クラス

抽象クラス(abstract class)の書き方
クラス、メソッドいずれもabstractを先頭につける。

abstract class Greeter {
  open fun sayHello() {
    println("Hello")
  }
  abstract fun sayGoodbye()
}

class JapaneseGreeter: Greeter {
  override fun sayHello() {
    println("こんにちは")
  }
  override fun sayGoodbye() {
    println("さようなら")
  }
}

val greeter = JapaneseGreeter()
greeter.sayHello()
> こんにちは
greeter.sayGoodbye()
> さようなら

ここはそんなにJavaとかPHPと変わんない。
抽象プロパティも宣言できる。

abstract class Greeter {
  abstract val name: String
  abstract fun introduceMysalf()
}

abstract class JapanaseGreeter(override val name: String): Greeter {
  override fun introduceMysalf() {
    println("私は${name}です")
  }
}

val greeter = JapanaseGreeter("麻里奈")
greeter.introduceMysalf()
> 私は麻里奈です

インターフェース

これはあんまりJavaとかわんね。
一般的なクラス志向の言語にある機能。(めんどくさくなってきた)

インターフェースはメソッドに実装を持たせることはできるが、プロパティを定義することはできない。

// 基本的なインターフェースの使い方
interface InterfaceA {
  fun foo()
  fun bar()
}

interface InterfaceB {
  fun bar()
  fun baz()
}

class MyClass: InterfaceA, InterfaceB {
  override fun foo() {}
  override fun bar() {}
  override fun baz() {}
}

// メソッドの実装することができるよ
intarface InterfaceA {
  fun foo() {
    println("A")
  }
}

intarface InterfaceB {
  fun foo() {
    println("B")
  }
}

class MyClass: InterfaceA, InterfaceB {
  override fun foo() {
    super<InterfaceA>.foo() // InterfaceAの実装を使う
  }
}

シングルトンオブジェクト

シングルトンにしたい場合はclassではなくobjectを使う

object EnglishGreeter: Greeter {
  override fun sayHello() {
    println("Hello")
  }
}

EnglishGreeter.sayHello() // EnglishGreeterはオブジェクトなので直接そのメンバにアクセスできる
> Hello

// こういう使い方もできる
val englishGreeter: EnglishGreeter = EnglishGreeter // ()は無いよ!
englishGreeter.sayHello()