とほほのKotlin入門

トップ > とほほのKotlin入門

目次

Kotlinとは

Kotlinを試してみる

インストール

OS X, Linux, Cygwin, FreeBSD 等にインストールするには下記を実行します。

$ curl -s https://get.sdkman.io | bash
$ source "/root/.sdkman/bin/sdkman-init.sh"
$ sdk install kotlin
$ sdk install java
$ kotlinc -version
info: kotlinc-jvm 1.9.22 (JRE 21.0.2+13-LTS)
Hello world

Hello, World! を出力するプログラムは次のようになります。hello.kt など、拡張子には .kt をつけます。

fun main() {
    println("Hello, World!")
}

コンパイルと実行は次のようにします。

$ kotlinc hello.kt -include-runtime -d hello.jar
$ java -jar hello.jar
Hello, World!

基本的な覚え事

コマンド引数

コマンド引数を受け取るには次のようにします。

fun main(args: Array<String>) {
    println(args.contentToString())
}
print, println

print は標準出力に文字列を書きだします。println は標準出力に文字列を書き出し、最後に改行をつけます。

print("Hello\n")
println("Hello")
コメント

コメントは ///* ... */ 形式を使用できます。/* ... */ はネストすることもできます。

// Comment...

/* Multiple
   lines comment...*/
   
/* This is a /* nested */ comment. */
行末のセミコロン

行末のセミコロン(;)は省略可能です。記述しても構いませんが省略することが多いようです。

println("Hello!")
println("Hello!");
コーディング規約

次のようなコーディング規約があります。

キーワード

// Hard keywords
as(cast)	as(import)	as?		break
class		continue	do		else
false		for		fun		if
in		!in		interface	is
!is		null		object		package
return		super		this		throw
true		try		typealias	typeof
val		var		when		while

// Soft keywords
by		catch		constructor	delegate
dynamic		field		file		finally
get		import		init		param
property	receiver	set		setparam
where

// Modifier keywords
actual		abstract	annotation	companion
const		crossinline	data		enum
expect		external	final		infix
inline		inner		internal	lateinit
noinline	open		operator	out
override	private		protected	public
reified		sealed		suspend		tailrec
vararg

// Special identifiers
field		it

変数

var は変更可能な(ミュータブルな)変数を定義します。

var a: Int = 1
var b = 2
var c: Int
c = 3

val は変更不可な(イミュータブルな)変数を定義します。

val MAX_COUNT: Int = 1024
val MAX_SIZE = 1024

const をつけると効率的な定数を宣言できます。トップレベル変数、名前付きオブジェクト、コンパニオンオブジェクトで使用できます。

const val MAX_SIZE = 1024

基本的な型として下記があります。

var a: Boolean = true		// 真偽値
var b: Byte = 123		// 8ビット整数(-128~127)
var c: Short = 123		// 16ビット整数(-32768~32767)
var d: Int = 123		// 32ビット整数(-231~231-1)
var e: Long = 123L		// 64ビット整数(-263~263-1)
var f: Float = 12.3F		// 32ビット浮動小数点数
var g: Double = 12.3		// 64ビット浮動小数点数
var h: Char = 'A'		// 文字
var i: String = "ABC"		// 文字列
真偽値(Boolean)

真偽値は true または false で表します。

var b1: Boolean = true
var b2: Boolean = false
数値(Byte, Short, Int, Long, Float, Double)

数値は下記のように指定します。8進数(0o)はサポートされていません。アンダーバー(_)は無視されます。

123		10進数(Int)
123L		10進数(Long)
0x0F		16進数
0b10111100	2進数
123.4		実数(Double)
1.23e2		実数(Double)
123.4f		実数(Float)
1.23e2		実数(Float)
12_345_678	桁区切り
文字(Char)

文字はシングルクォートで囲みます。

'A'

下記のエスケープシーケンスを利用できます。

\t		// タブ(TAB)
\b		// バックスペース(BS)
\n		// 改行(LF)
\r		// 復帰(CR)
\'		// シングルクォート(')
\"		// ダブルクォート(")
\\		// バックスラッシュ(\)
\$		// ドル記号($)
\uFF00	// Unicode文字
文字列(String)

文字列はダブルクォート(")で囲みます。上記のエスケープシーケンスも利用できます。

"ABC"

文字列の中に $${ ... } を用いることで、変数やプログラムを記述することができます。

println("$a + $b = ${add(a, b)}")

U+10000~U+10FFFF の文字はサロゲートペアとして2文字を使用して表現されます。

println("吉".length)	// 1
println("𠮷".length)	// 2(サロゲートペア)

"""...""" は生文字列を表します。改行やバックスラッシュが通常の文字として扱われます。

println("""\ is a backslash""")
println("""This is Japan.
That is America.""")
Any型(Any)

Any は任意の型を示します。

fun foo(arg: Any?) {
    when (arg) {
        is Int -> println("Int: $arg")
        is Char -> println("Char: $arg")
        is String -> println("String: $arg")
    }
}
型変換

下記のような型変換を行うことができます。

123.toString()		// IntからStringに変換
"123".toInt()		// StringからIntに変換
"3.14".toFloat()	// StringからFloatに変換(変換できない場合はnull)
1.23.toInt()		// DoubleからIntに変換(切り捨て)
1.23.roundToInt()	// DoubleからIntに変換(四捨五入) (import kotlin.math.roundToInt が必要)
配列

arrayOf() は配列を生成します。配列の個数が固定で、低機能ですがパフォーマンスに優れます。

var nums = arrayOf(1, 2, 3)
var cols = arrayOf("Red", "Green", "Blue")

for (n in nums) { println(n) }
for (c in cols) { println(c) }

[ ... ] は配列の要素にアクセスします。配列の添え字(インデックス)は 0 から始まります。

println(nums[0])
println(cols[1])
リスト

listOf() はリストを生成します。配列が個数固定なのに対して、リストは .add() や .remove() で追加削除することができます。しかし、配列は nums[1] = 123 で値を変更できるのに対し、リストは nums[1] = 222 で値を変更することはできません。

var nums = listOf(1, 2, 3)
var cols = listOf("Red", "Green", "Blue")

for (n in nums) { println(n) }
for (c in cols) { println(c) }

下記のようにリストに対してフィルタをかけて偶数のみといった条件にマッチする要素を抽出することができます。

val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
println(nums.filter { it % 2 == 0 })
セット

セットはリストと似ていますが、重複した値を許可しない点が異なります。

var s = mutableSetOf("Red", "Green", "Blue")
s.add("Red")
println(s)		// [Red, Green, Blue]
マップ

マップはキーと値のペアを扱います。

var map = mapOf("Red" to "#f00", "Green" to "#0f0", "Blue" to "#00f")
println(map["Red"])
for ((key, value) in map) {
    println("$key = $value")
}
レンジ(range)

.. は範囲を示すレンジを作成します。in は左辺が、右辺で示すレンジの範囲内か否かを判断します。

for (i in 1..10) println(i)

1..10 は 1.rangeTo(10) と同じ意味を持ちます。

for (i in 1..10) println(i)
for (i in 1.rangeTo(10)) println(i)

if文や while文の中で使用することもできます。

if (n in 1..10) println("OK")

範囲内に無いことを確認するには !in を使用します。

if (n !in 1..10) println("Out of range.")

downTo は減少するレンジを作成します。step は n個飛ばしのレンジを作成します。

for (i in 10 downTo 1 step 2) println(i)

Int 型の他、文字(Char)、文字列(String)、実数(Double)型などでも使用することができます。

if ('b' in 'a'..'z') println("OK")
if ("gz" in "aa".."zz") println("OK")
if (1.23 in 0.00..9.99) println("OK")
型を確認する(is, !is)

is は左辺が右辺の型にマッチする場合に真、!is はマッチしない場合に真となります。サブクラスのインスタンスは、親クラスにマッチします。

println("ABC" is String)	// true
println("ABC" !is String)	// false

open class ClassA() {}
class ClassB(): ClassA() {}
var a = ClassA()
var b = ClassB()
println(a is ClassA)		// true
println(a is ClassB)		// false
println(b is ClassA)		// true
println(b is ClassB)		// true

when を用いて確認することもできます。

fun foo(a: Any) {
    when (a) {
        is String -> println("String")
        is Char -> println("Char")
        else -> println("Unknown")
    }
}
型の別名(typealias)

typealias を用いて型に別名をつけることができます。

typealias Meter = Int
typealias Killometer = Int
キャスト(as, as?)

as は型をキャストする際に用います。下記の例では、String 型ではない変数 a を String 型に変換(キャスト)して s に代入しています。キャストできない場合は例外が発生します。

var s: String = a as String

as を用いて、スーパークラスをサブクラスにキャストすることもできます。

open class Parent() {}
class Child(): Parent() {}
var p: Parent = Child()
var c: Child = p as Child

キャストできない場合は例外が発生しますが、as? を用いるとキャストできない場合に null を返します。

var n1: Short? = 123
var n2: Int? = n1 as? Int?
ダイナミック型(dynamic)

Kotlin は基本的には静的型付け言語ですが、JavaScript にコンパイルする際には dynamic を用いて動的型付けを行うことができます。

var a: dynamic
a = 123		// 数値を代入
a = "ABC"	// 文字列を代入

演算子

下記などの演算子を利用できます。

expr1 + expr2		// 加算
expr1 - expr2		// 減算
expr1 * expr2		// 乗算
expr1 / expr2		// 除算
expr1 % expr2		// 剰余

var = value		// 代入
var += value		// var = var + value と等価
var -= value		// var = var - value と等価
var *= value		// var = var * value と等価
var /= value		// var = var / value と等価
var %= value		// var = var % value と等価

+var			// 正数
-var			// 負数
var++			// インクリメント(インクリメント前の値を返す)
++var			// インクリメント(インクリメント後の値を返す)
var--			// デクリメント(デクリメント前の値を返す)
--var			// デンクリメント(デクリメント後の値を返す)

expr1 == expr2		// 等しい
expr1 != expr2		// 等しくない
expr1 === expr2		// 等しい(参照比較)
expr1 !== expr2		// 等しくない(参照比較)
expr1 < expr2		// より小さい
expr1 <= expr2		// 以下
expr1 > expr2		// より大きい
expr1 >= expr2		// 以上

!expr1			// 否定(NOT)
expr1 && expr2		// 論理積(AND)
expr1 || expr2		// 論理和(OR)

arr[...]		// 配列要素を参照
*arr			// スプレッド演算子(配列を展開)
n..m			// レンジ
var in arr		// 含んでいる

var?.prop		// varがnullであればnull、さもなくばvar.prop(セーフコール)
var ?: expr		// varがnullでなければvar、さもなくばexpr(エルビス演算子)
var!!			// varがnullでないことを断言

args -> expr		// ラムダ式
type1 -> type2		// 関数の型定義
cond -> stmt		// when式の条件と処理

::func			// 関数参照
@annotation		// アノテーション
label@			// ラベル

制御構文

if式

if (expr) stmt1 else stmt2 は、もし expr が真であれば stmt1 を、さもなければ stmt2 を実行します。

if (a > b) {
    println("Big")
} else {
    println("Small")
}

式として利用することもできます。

var c = if (a > b) a else b
fotループ

for (... in ...) ... は、レンジや配列やコレクションに対して繰り返し処理を行います。

for (i in 1..10) {
    println(i)
}

for (i in 10 downTo 0 step 2) {
    println(i)
}

var colors = arrayOf("Red", "Green", "Blue")
for (color in colors) {
    println(color)
}
forEachループ

forEachforEachIndexed は配列やコレクションに対して処理を繰り返します。

var a = arrayOf("Red", "Green", "Blue")

a.forEach {
    value -> println(value)
}

a.forEachIndexed {
    index, value -> println("${index}: ${value}")
}
whileループ

while は条件が成り立つ間処理を繰り返します。

var i = 10
while (i > 0) {
    println(i--)
}
do-whileループ

do ... while は条件が成り立たなくなるまでループを繰り返します。

var i = 10
do {
    println(i--)
} while (i > 0)
break文

break は一番内側のループを抜けます。

for (i in 1..5) {
    if (i == 2) {
        break
    }
    println(i)
}
continue文

continue は一番内側のループの次のループ処理を始めます。

for (i in 1..5) {
    if (i == 2) {
        continue
    }
    println(i)
}
when式

when は条件によって異なる値を返します。is 型名で、型によって処理を振り分けることもできます。

fun describe(obj: Any): String =
    when (obj) {
        1          -> "One"
        in 2..3    -> "Two or Three"
        "Hello"    -> "Greeting"
        is Long    -> "Long"
        !is String -> "Not a string"
        else       -> "Unknown"
    }
ラベル(@)

@ でラベルを指定することで、breakcontinue の対象ループを指定することができます。

foo@ for (i in 1..5) {
    for (j in 1..5) {
        if (i == 3 && j == 3) break@foo
        println("$i $j")
    }
}

関数内のラムダ式で return すると通常は関数から return して関数が終了してしまいますが、@ でラベル指定することで、ラムダ式のみを return することが可能となります。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach baa@ {
        if (it == 3) return@baa
        println(it)		// 1, 2, 4, 5
    }
}

クラスや関数は暗黙的にクラス名、関数名のラベルを持っています。下記のように内部クラスから外部クラスのインスタンスを参照することも可能となります。

class A {
    val foo = "AAA"
    inner class B {
        val foo = "BBB"
        fun foo() = println("${this.foo} ${this@A.foo}")	// BBB AAA
    }
}

fun main() {
    A().B().foo()
}
例外処理(try, throw, catch, finally)

例外処理は次のような例となります。try は例外処理を開始します。throw は例外を投げます。catch は例外を捕捉します。finally はどの catch にも捕捉されなかった例外に対する処理を記述します。

try {
    if (...) {
        throw IllegalStateException()
    }
} catch (e: Exception) {
    println(e)
} finally {
    println("Finally")
}

関数

関数

fun は関数を定義します。return は関数の戻り値を返します。

fun sum(a: Int, b: Int): Int {
    return a + b
}

もしくは、次のように定義することもできます。

fun sum(a: Int, b: Int) = a + b
デフォルト引数

引数を省略した場合のデフォルト値を指定することができます。

fun printMessage(msg: String, maxLines: Int = 100) {
    :
}

デフォルト引数は、呼び出し時に引数名を記述することで指定したい引数のみを渡すことができます。

fun drawCircle(x: Int, y: Int, r: Int,
    lineColor: String = "#000",
    fillColor: String = "transparent",
    borderWidth: String = "1px") { ... }

fun main() {
    drawCircle(100, 100, 50, borderWidth="2px")
}
Unit関数

値を返却しない関数は Unit関数として扱われます。関数の型は Unit となりますが、関数戻り値の型宣言を省略することができます。

fun printMessage(msg: String) {      // : Unit が不要
    println(msg)
}
Nothing型関数

常に例外を返却し、戻り値を戻すことのない関数は Nothing型関数として定義します。

fun raiseError(msg: String): Nothing {
    throw MyException(msg)
}
可変引数

vararg は可変引数を実現します。

fun foo(vararg args: String) {
    args.forEach {
        println(it)
    }
}

fun main() {
    foo("A", "B", "C")
}

可変引数関数に配列を変数として渡す際には、スプレッド演算子(*)を用いて配列を展開してから渡す必要があります。

fun main() {
    val arr = arrayOf("A", "B", "C")
    foo(*arr)
}
無名関数とラムダ式

関数名を省略した関数を無名関数と呼びます。無名関数は変数に代入して利用したり、コールバック関数に指定したります。

fun main() {
    var add1 = fun(a: Int, b: Int): Int = a + b

    println(add1(3, 5))
}

下記のような書き方もできます。これをラムダ式と呼びます。

fun main() {
    var add2 = { a: Int, b: Int -> a + b }
    var add3: (Int, Int) -> Int = { a, b -> a + b }

    println(add2(3, 5))
    println(add3(3, 5))
}

引数が1つのみのラムダ関数では、引数を it で参照することができます。

var square: (Int)->Int = { it * it }
println(square(5))	// 25
インライン関数

inline を用いて関数をインライン関数としてコンパイルすることができます。プログラムサイズは大きくなりますが、関数コールのオーバーヘッドが無くなるため処理は早くなります。コレクションの要素を繰り返し処理する際などには効果的です。本当にインライン化するべきなのか確認を促すためにコンパイル時にワーニングが出ます。

inline fun foo() {
    println("Foo")
}

インライン関数では引数のラムダ式もインライン化されますが、noinline をつけることでインライン化されないようにできます。

inline fun foo(n: Int, noinline lambda: (Int) -> Int) {
    println(lambda(n))
}

fun main() {
    foo(3, { n -> n * 2 })
}

インライン関数の引数のラムダ関数を関数内のローカルオブジェクトやネストした関数から呼び出そうとするとエラーとなります。この場合、crossinline を用いることで呼び出せるようになります。

inline fun foo(n: Int, crossinline lambda: (Int) -> Int) {
    object: Runnable {
       override fun run() = println(lambda(n))
    }.run()
}

fun main() {
    foo(3, { n -> n * 2 })
}
拡張関数

既存のクラスに後からメソッドを追加することができます。Int や String などの基本の型(クラス)にもメソッドを追加することができます。

fun String.hello() = println("Hello $this")

fun main() {
    "Yamada".hello()
}
中間記法関数(infix)

infix は中間記法に対応した関数を定義します。下記は Int クラスに add メソッドを拡張定義しています。3.add(5) の様に呼び出すことも可能ですが、infix をつけることにより、3 add 5 のように中間記法で使用することが可能となります。このように定義された中間記法関数には、Range を生成する untill や Pair を生成する to などがあります。

infix fun Int.add(x: Int): Int {
    return this + x
}

fun main() {
    println(3.add(5))		// 8
    println(3 add 5)		// 8
}
nullとNull許容型

null は値が存在しないことを意味します。通常の変数には null を代入することはできません。型名に ? をつけると null を許容する変数(Nullableな変数)を定義することができます。

var a: String = null		// nullは代入できない
var b: String? = null		// nullを代入できる

Nullableな変数はそのままでは、プロパティやメソッドを呼び出すことができません。事前に null ではないことを確認したり(スマートキャスト)、null の場合は式全体が null になるようにしたり(セーフコール)、?. 演算子や !! 演算子を用いる必要があります。

fun main() {
    var a: String? = "ABC"
    var b: String? = "ABC"
    var c: String? = "ABC"
    var d: String? = "ABC"

    // nullでないことを確認したif文の中では Non-nullable として扱われる(スマートキャスト)
    if (a != null) { println(a.length) }

    // ?. を用いると b が null の場合は式全体が null となる(セーフコール)
    println(b?.length)

    // エルビス演算子(?:)を用いてnullにはならないようにする
    d = d ?: "(Unknown)"
    println(d.length)

    // !! を用いると強制的に使用できる(実行時エラーとなる可能性があるので危険)
    println(c!!.length)
}
関数参照(::)

関数名の前に :: をつけると、関数を参照するオブジェクトを得ることができます。下記の例では add 関数を関数オブジェクトとして method に代入し、呼び出しています。

fun add(x: Int, y: Int): Int = x + y

fun main() {
    val method = ::add
    println(method(3, 5))
}

クラス

クラス(class)

class はクラスを定義します。

class Person(var name: String, var age: Int) {
    fun print() {
        println("${this.name} ${this.age}")
    }
}

fun main() {
    var p1 = Person("Yamada", 26)
    p1.print()
}
コンストラクタ(constructor)

constructor はクラスのインスタンスが生成される際に自動的に呼び出されるコンストラクタを定義します。

class Person constructor(_name: String) {
    var name = _name
}

fun main() {
    var p1 = Person("Yamada")
    println(p1.name)            // Yamada
}

コンストラクタに可視性修飾子やアノテーションを指定する必要が無い場合は constructor を省略することができます。

class Person(_name: String) {
    var name = _name
}

コンストラクタが単にプロパティを初期化するだけであれば、下記のように省略することもできます。

class Person(var name: String) {
}

上記のコンストラクタをプライマリコンストラクタと呼びます。また、下記のようにセカンダリコンストラクタを定義することもできます。セカンダリコンストラクタは引数の数や型に応じて複数記述することができます。

class Person {
    var name: String
    var age: Int = -1
    constructor(_name: String) { name = _name }
    constructor(_name: String, _age: Int) { name = _name; age = _age }
}

クラスがプライマリコンストラクタを持つ場合、this を用いてプライマリコンストラクタを呼び出す必要があります。

class Person(var name: String) {
    var age: Int = -1
    constructor(_name: String, _age: Int) : this(_name) { age = _age }
}
初期化ブロック

クラスには init { ... } で初期化ブロックを記述することができます。初期化ブロックはコンストラクタが呼び出されるよりも前に呼び出されます。

class Foo {
    init {
        println("Foo is created.")
    }
}
クラスの継承

下記のようにして、親クラス(Animal)を継承するサブクラス(Cat)を定義することができます。通常のクラスはサブクラスを作成することはできません。open を付加することにより、サブクラスを生成可能なクラスであることを示します。

open class Animal() { ... }
class Cat : Animal() { ... }

Kotolin のクラスのデフォルトは final で、サブクラスを作成することはできません。

final class Animal() { ... }
class Cat : Animal() { ... }	// Error

override で親クラスのメソッドをオーバーライドすることができます。オーバーライドされるメソッドは open 宣言しておく必要があります。

open class Foo {
    open fun print() { println("Foo") }
}

class Baa(): Foo() {
    override fun print() { println("Baa") }
}

親クラスのコンストラクタを呼び出すには super() を用います。

open class Animal(var type: String)

class Cat: Animal {
    var name: String
    constructor(_name: String): super("Cat") {
        name = _name
    }
}

もしくは次のように記述することもできます。

open class Animal(var type: String)
class Cat(var name: String): Animal("Cat") {}
クラスのネスト

クラスはネストすることができます。

class A {
    class B { ... }
}

var obj = A.B()

inner はネストされたクラスが内部クラスであることを示します。

class A {
    inner class B { ... }
}

var obj = A().B()
setter/getter

クラスが持つプロパティには set() で値設定時メソッド(setter)、get() で値参照時メソッド(getter) を指定することができます。メソッド内では field はプロパティの値を示します。

class Foo() {
    var name: String = ""
        set(value) {
            println("---set---")
            field = value
        }
        get() {
            println("---get---")
            return field
        }
}

fun main() {
    var a = Foo()
    a.name = "Yamada"	// ---set---
    println(a.name)	// ---get---
}
抽象クラス(abstract)

abstract は抽象クラスを定義します。抽象クラスは抽象メソッドや抽象プロパティを持ちます。抽象クラス自身はそのままではインスタンス化することができず、サブクラスで継承し、抽象メソッドや抽象プロパティを実装してからインスタンス化します。サブクラスが実装すべきメソッドやプロパティのルールを決めるのが抽象クラスです。

abstract class Foo {			// 抽象クラス
    abstract var name: String		// 抽象プロパティ
    abstract fun hello(): String	// 抽象メソッド
}

class Baa : Foo() {				// 抽象クラスを実装するサブクラス
    override var name: String = "..."		// 抽象プロパティを実装
    override fun hello(): String = "Hello"	// 抽象メソッドを実行
}

fun main() {
    var b = Baa()
    println(b.name)
    println(b.hello())
}
インタフェース(interface)

interface はインタフェースを定義します。インタフェースも抽象クラスと同様、自信ではインスタンスすることができず、インタフェースを実装する定義すべきプロパティやメソッドを定義します。抽象クラスが「継承」されることを意図しているのに対し、インタフェースはサブクラスが兼ね備える「特性」を定義するもので、ひとつのサブクラスが複数のインタフェースを実装することができます。下記の例では、Printable や Writable という名前のインタフェースを定義しています。Printable インタフェースを実装するクラスでは、print() メソッドを、Writable インタフェースを実装するクラスでは、write() メソッドをサポートする必要があります。

interface Printable { fun print() }
interface Writable { fun write() }

class Animal(var type: String) : Printable, Writable {
    override fun print() = println("PRINT!!")
    override fun write() = println("WRITE!!")
}
可視性修飾子

可視性修飾子は変数や関数などの参照範囲を指定します。デフォルトは public ですべての場所から参照されます。internal は自モジュールからのアクセスに限定します。private は自ファイルからのアクセスに限定します。

package foo

public var a1: String = "A1"
internal val a2: String = "A2"
private var a3: String = "A3"

クラスメンバに指定する場合、デフォルトは public ですべての場所から参照されます。internal は自モジュールからのアクセスに限定します。protected は自クラスおよびサブクラスからのアクセスに限定します。private は自クラスからのアクセスに限定します。

class Foo {
    public var a1: String = "A1"
    internal val a2: String = "A2"
    private var a3: String = "A3"
    protected val a4: String = "A4"
}
列挙型(enum)

enum は列挙型クラスを生成します。

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

enum class Color(val rgb: String) {
    RED("#f00"),
    GREEN("#0f0"),
    BLUE("#00f"),
}

fun main() {
    println(Direction.NORTH)	// NORTH
    println(Color.RED)		// RED
    println(Color.RED.name)	// RED
    println(Color.RED.rgb)	// #f00
}
.toString()

クラスはすべて文字列化のための .toString() メソッドを持ちます。println() で出力する際などに暗黙的に呼ばれます。下記のように上書きすることもできます。

class Person(val name: String, var age: Int) {
    override fun toString() = "$name($age)"
}

fun main() {
    println(Person("Yamada", 26))	// Yamada(26)
}
.equals()

すべてのクラスは比較のための .equals() メソッドを持ちます。== で比較する際などに暗黙的に呼ばれます。下記のように上書きすることもできます。

class Foo(val name: String) {
    override fun equals(other: Any?) = this.name == (other as Foo).name
}

fun main() {
    val a1 = Foo("Yamada")
    val a2 = Foo("Yamada")
    println(a1 == a2)		// true
}
データクラス(data class)

data はデータクラスを生成します。データクラスは通常のクラスとほぼ同様に利用できますが、.toString() がプロパティ値も含むようになり、また、プロパティ値もコピーする .copy() メソッドが自動的に生成されます。

class Foo1(var name: String)		// 通常のクラス
data class Foo2(var name: String)	// データクラス

fun main() {
    var a1 = Foo1("Yamada")
    println(a1)			// Foo1@2280cdac

    var a2 = Foo2("Yamada")
    println(a2)			// Foo2(name=Yamada)

    var a3 = a2.copy()
    println(a3)			// Foo2(name=Yamada)
}
委譲(delegation)

by は委譲を行います。例えば、Base インタフェースを実装する BaseImpl クラスがあるとします。ほぼ BaseImpl クラスの機能でよいのに、BaseImpl の一部だけを一時的に拡張した機能を利用したい場合、Base インタフェースが要求するプロパティやメソッドを全て再定義するのではなく、一部の機能のみを拡張した BaseImplEx クラスを作成して一時的に利用することができます。

interface Base {
    fun funcA()
    fun funcB()
    fun funcC()
}

class BaseImpl(): Base {
    override fun funcA() { println("AAA") }
    override fun funcB() { println("BBB") }
    override fun funcC() { println("CCC") }
}

class BaseImplEx(b: Base): Base by b {
    override fun funcA() { println("AAA!!!") }
}

fun main() {
    val obj = BaseImpl()
    obj.funcA()			// AAA
    BaseImplEx(obj).funcA()	// AAA!!!
}

上記は Class Delegation と呼ばれるものですが、Property Delegation として、プロパティへのアクセス(setやget)を他のクラスに委譲することもできます。

import kotlin.reflect.KProperty

class Example {
    var p: String by Delegate()
    override fun toString() = "Example"
}

class Delegate() {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "-- Get ${prop.name} from $thisRef"
    }
    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        println("-- Set \"$value\" to ${prop.name} in $thisRef")
    }
}

fun main() {
    val e = Example()
    e.p = "NEW STRING"	// -- Set "NEW STRING" to p in Example
    println(e.p)	// -- Get p from Example
}

オブジェクト(object)

オブジェクト式

オブジェクト式は、クラスの無名インスタンスを生成します。元クラスのプロパティやメソッドをオーバライドすることもできます。下記の例では MouseAdapter クラスをオーバーライドし、その無名インスタンスを作成し、addMouseListener() の引数として渡しています。

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }
    override fun mouseEntered(e: MouseEvent) { ... }
})
シングルトン(名前付きオブジェクト)

オブジェクト宣言は、唯一のインスタンスを持つシングルトンクラスを宣言します。名前付きオブジェクトとも呼ばれます。下記の例では Config という名前のシングルトンを生成しています。

object Config {
    var maxCount = 100
}
println(Config.maxCount)

シングルトンのメソッドはスタティックメソッドの様に、クラス名.メソッド名 で呼び出すことができます。

object Foo {
    fun hello() = println("Hello!")
}

fun main() {
    Foo.hello()
}
コンパニオンオブジェクト(スタティックメソッド)

コンパニオンオブジェクトは、companion と共に用いて、クラス内でスタティックメソッドを定義するのに使用されます。

class Math {
    companion object {
        fun add(a: Int, b: Int) = a + b
    }
}
println(Math.add(3, 5))

下記の様に Factory Method を実装する際にも利用されます。

class User(var name: String) {
    companion object {
        fun new(name: String): User {
            return User(name)
        }
    }
}

fun main() {
    var u = User.new("Yamada")
    println(u.name)
}

ジェネリック

ジェネリッククラス

引数を Any 型で受け取り、Any 型のプロパティに格納することにより、Int, Char, String など、様々な型に応じたクラスやメソッドを定義することができます。

class Foo(val value: Any)

fun main() {
    val a1 = Foo(123)
    println(a1.value)		// 123
}

しかし、value は Any 型なので、下記のように value に対して + 1 の演算を行おうとするとエラーとなります。

class Foo(val value: Any)

fun main() {
    val a1 = Foo(123)
    println(a1.value + 1)	// Error
}

<T> でジェネリック型を定義することにより、value は Int, Char, String など引数に応じた型のプロパティとして扱われることとなり、この問題を解決することができます。

class Foo<T>(val value: T)

fun main() {
    val a1 = Foo(123)
    println(a1.value + 1)	// 124
}

下記のようにして型に制約をかけることができます。下記では、Int や Double など、Number のサブクラスに制約しています。

class Foo<T: Number>(val value: T)
ジェネリック関数

関数でもジェネリックを使用することができます。下記は任意の型 T の引数を受け取り、T 型の値を返却する関数です。呼び出し時に型を指定しますが、引数により型が明確な場合は省略することができます。

fun <T> foo(x: T): T { return x }

fun main() {
    println(foo<Int>(123))
    println(foo<String>("ABC"))
    println(foo(123))
    println(foo("ABC"))
}

下記のようにして型に制約をかけることができます。下記では、Int や Double など、Number のサブクラスに制約しています。

fun <T: Number> foo(x: T): T { ... }

制約を複数指定したい場合は where を用います。

fun <T> foo(x: T) where T: Runnable, T: Cancellable { ... }

その他

パッケージ(package)

package はパッケージを定義します。

package org.example

fun exampleFunction() { ... }
class ExampleClass { ... }
インポート(import)

import はパッケージをインポートします。as はインポートしたエンティティに対して別名をつけます。

import org.example.ExampleClass
import org.example.*
import org.example.ExampleClass as SampleClass
演算子のカスタマイズ(operator)

plus()minus() などの演算子メソッドを定義することで、+- などの演算子をカスタマイズすることができます。

data class Complex(var real: Double, var imag: Double) {
    operator fun plus(other: Complex): Complex {
        return Complex(real + other.real, imag + other.imag)
    }
}

fun main() {
    var c1 = Complex(1.0, 2.0)
    var c2 = Complex(3.0, 4.0)
    println(c1 + c2)			// Complex(real=4.0, imag=6.0)
}

演算子と演算子メソッドの間には次の関係があります。

a + b		a.plus(b)
a - b		a.minus(b)
a * b		a.times(b)
a / b		a.div(b)
a % b		a.rem(b)	// mod()からrem()に変更
a += b		a.plusAssign(b)
a -= b		a.minusAssign(b)
a *= b		a.timesAssign(b)
a /= b		a.divAssign(b)
a %= b		a.modAssign(b)
+a		a.unaryPlus()
-a		a.unaryMinus()
!a		a.not()
++a		a.inc()
--a		a.dec()
a == b		a?.equals(b) ?: (b === null)
a != b		!(a?.equals(b) ?: (b === null))
a < b		a.compareTo(b) > 0
a > b		a.compareTo(b) < 0
a <= b		a.compareTo(b) >= 0
a >= b		a.compareTo(b) <= 0
!var		a.not()
a..b		a.rangeTo(b)
a in b		a.contains(b)
a !in b		!a.contains(b)
a[i]		a.get(i)
a[i, j]		a.get(i, j)
a[i] = b	a.set(i, b)
a[i, j] = b	a.set(i, j, b)
a()		a.invoke()
a(i)		a.invoke(i)
a(i, j)		a.invoke(i, j)
遅延初期化(lateinit, by lazy)

クラスのプロパティは初期化が必要ですが、lateinit を用いることで初期化を遅らせることができます。Int などのプリミティブ型には使用できない。Null許容型には使用できない。var 変数にしか使用できないなどの制限があります。

class Foo {
    lateinit var name: String		// init()が呼ばれるまで初期化不要
    fun init(name: String) { this.name = name }
}

lateinit と似たものに by lazy があります。Int や Null許容型にも指定可能ですが、val 変数にしか使用できません。宣言部には初期化のためのラムダ式を記述します。初めてその値が必要になった(参照された)時点で初期化され、値が参照されなかった場合は初期化も不要となります。

fun connectToDB() {
    ...
    return db
}
fun main() {
    val db by lazy { connectToDB() }
}
マルチプラットフォーム開発(expect・actual)

expectactual は、Android と iOS など、マルチプラットフォームのアプリケーションを開発する際に使用します。共通部では expect を用いて期待するクラスなどを定義します。また、Android や iOS それぞれの実装で actual を用いてに依存したプラットフォーム依存のコードを記述します。

// 共通部での実装
expect class Foo {
    fun getPlatformName(): String
}
// Android依存部での実装
actual class Foo {
    fun getPlatformName(): String = "Android"
}
外部呼出し(external)

external は、C++ で記述されたコードを JNI 経由で呼び出したり、JavaScript コードを呼び出す際に使用します。

external fun foo(...) { ... }
型の具現化(reified)

JVM の仕様によりジェネリック関数の型は通常実行時には失われますが、Kotlin では inlinereified を用いることで、実行時にもジェネリック関数の型を参照することが可能となります。

fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

Copyright (C) 2021-2024 杜甫々
初版:2021年5月4日 最終更新:2024年3月10日
http://www.tohoho-web.com/ex/kotlin.html