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.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("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" // 文字列
真偽値は true または false で表します。
var b1: Boolean = true var b2: Boolean = false
数値は下記のように指定します。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 桁区切り
文字はシングルクォートで囲みます。
'A'
下記のエスケープシーケンスを利用できます。
\t // タブ(TAB) \b // バックスペース(BS) \n // 改行(LF) \r // 復帰(CR) \' // シングルクォート(') \" // ダブルクォート(") \\ // バックスラッシュ(\) \$ // ドル記号($) \uFF00 // Unicode文字
文字列はダブルクォート(")で囲みます。上記のエスケープシーケンスも利用できます。
"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 は任意の型を示します。
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") }
.. は範囲を示すレンジを作成します。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 はマッチしない場合に真となります。サブクラスのインスタンスは、親クラスにマッチします。
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 Meter = Int typealias Killometer = Int
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?
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 (expr) stmt1 else stmt2 は、もし expr が真であれば stmt1 を、さもなければ stmt2 を実行します。
if (a > b) { println("Big") } else { println("Small") }
式として利用することもできます。
var c = if (a > b) a else b
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 や forEachIndexed は配列やコレクションに対して処理を繰り返します。
var a = arrayOf("Red", "Green", "Blue") a.forEach { value -> println(value) } a.forEachIndexed { index, value -> println("${index}: ${value}") }
while は条件が成り立つ間処理を繰り返します。
var i = 10 while (i > 0) { println(i--) }
do ... while は条件が成り立たなくなるまでループを繰り返します。
var i = 10 do { println(i--) } while (i > 0)
break は一番内側のループを抜けます。
for (i in 1..5) { if (i == 2) { break } println(i) }
continue は一番内側のループの次のループ処理を始めます。
for (i in 1..5) { if (i == 2) { continue } println(i) }
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" }
@ でラベルを指定することで、break や continue の対象ループを指定することができます。
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 はどの 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 となりますが、関数戻り値の型宣言を省略することができます。
fun printMessage(msg: String) { // : Unit が不要
println(msg)
}
常に例外を返却し、戻り値を戻すことのない関数は 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 は中間記法に対応した関数を定義します。下記は 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 を許容する変数(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 Person(var name: String, var age: Int) { fun print() { println("${this.name} ${this.age}") } } fun main() { var p1 = Person("Yamada", 26) p1.print() }
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()
クラスが持つプロパティには 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 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 はインタフェースを定義します。インタフェースも抽象クラスと同様、自信ではインスタンスすることができず、インタフェースを実装する定義すべきプロパティやメソッドを定義します。抽象クラスが「継承」されることを意図しているのに対し、インタフェースはサブクラスが兼ね備える「特性」を定義するもので、ひとつのサブクラスが複数のインタフェースを実装することができます。下記の例では、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 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() メソッドを持ちます。println() で出力する際などに暗黙的に呼ばれます。下記のように上書きすることもできます。
class Person(val name: String, var age: Int) {
override fun toString() = "$name($age)"
}
fun main() {
println(Person("Yamada", 26)) // Yamada(26)
}
すべてのクラスは比較のための .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 はデータクラスを生成します。データクラスは通常のクラスとほぼ同様に利用できますが、.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) }
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 }
オブジェクト式は、クラスの無名インスタンスを生成します。元クラスのプロパティやメソッドをオーバライドすることもできます。下記の例では 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 org.example fun exampleFunction() { ... } class ExampleClass { ... }
import はパッケージをインポートします。as はインポートしたエンティティに対して別名をつけます。
import org.example.ExampleClass import org.example.* import org.example.ExampleClass as SampleClass
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 を用いることで初期化を遅らせることができます。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 は、Android と iOS など、マルチプラットフォームのアプリケーションを開発する際に使用します。共通部では expect を用いて期待するクラスなどを定義します。また、Android や iOS それぞれの実装で actual を用いてに依存したプラットフォーム依存のコードを記述します。
// 共通部での実装
expect class Foo {
fun getPlatformName(): String
}
// Android依存部での実装
actual class Foo {
fun getPlatformName(): String = "Android"
}
external は、C++ で記述されたコードを JNI 経由で呼び出したり、JavaScript コードを呼び出す際に使用します。
external fun foo(...) { ... }
JVM の仕様によりジェネリック関数の型は通常実行時には失われますが、Kotlin では inline と reified を用いることで、実行時にもジェネリック関数の型を参照することが可能となります。
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? }