メモ > 技術 > プログラミング言語: Kotlin > 基本の文法
基本の文法
※基本的にPlaygroundで試したもの
Kotlin のプレイグラウンド | Android デベロッパー | Android Developers
https://developer.android.com/training/kotlinplayground?hl=ja
スタンドアロンなKotlinプログラムを実行する方法 - KOMMLOGG
https://kommkett.co.jp/memo-kotlin-runenv/
「Kotlin のプレイグラウンド」で実行する場合、main関数が必要になるので注意
REPL(Read-Eval-Print Loop)を使う場合、AndroidStudioから
Tool → Kotlin → Kotlin REPL → HelloAndroid
から実行して命令を入力できる
■ハローワールド
以下はPlaygroundの例(main関数が必要)
fun main() {
println("Hello, world!")
}
以下はREPLの例(main関数が不要)
println("Hello, world!")
■変数
fun main() {
// Int型の定数を宣言
val a: Int = 1000
// 型の定義を省略(型推論)
val b = 2000
// Int型の変数を宣言
var c: Int = 3000
c = 4000
// 結果を出力
println(a)
println(b)
println(c)
}
■文字列
fun main() {
// 文字列を代入
val message1 = "Kotlin"
// 複数行の文字列を代入
val multiline = """Hello
Kotlin
World."""
// 複数行の文字列を代入2(trimMargin()で「|」より前のインデント用スペースを削除)
val multiline2 = """Hello
|Kotlin
|World.""".trimMargin()
// 結果を出力
println(message1)
println(multiline)
println(multiline2)
// 文字列テンプレート
println("message1 = $message1")
println("$message1 length is ${message1.length}")
}
■型の変換(キャスト)
fun main() {
// 文字列を定義
val message1 = "64"
println(message1)
// 文字列を数値へ変換
val number1: Int = message1.toInt()
println(number1)
// 変換に失敗(例外が発生する)
/*
val message2 = "Kotlin"
val number2: Int = message2.toInt()
println(number2)
*/
// 変換できない場合はnullにする(null許容型に格納する)
val message3 = "Kotlin"
val number3: Int? = message3.toIntOrNull()
println(number3)
}
■null非許容型とnull許容型
fun main() {
// 代入できず「Null can not be a value of a non-null type String」のエラーになる
/*
var message1: String = null
println(message1)
*/
// null許容の変数を定義する
var message2: String? = null
println(message2)
// null許容型だと、メソッドやプロパティをそのままでは使えない
/*
val message3: String? = "Kotlin"
println(message3.length)
*/
// nullチェックをした後だと、null非許容型として使える
val message4: String? = "Kotlin"
if (message4 != null) {
println(message4.length)
}
}
■配列
fun main() {
val numbers: Array<Int> = arrayOf(2, 4, 6, 8)
println(numbers[0])
println(numbers[1])
println(numbers[2])
println(numbers[3])
}
■条件分岐
fun main() {
val a = 10
val b = 20
// 条件分岐
if (a > b) {
println("aはbより大きいです。")
} else {
println("aはbより大きくありません。")
}
// 値を返す条件分岐
val max = if (a > b) {
println("aはbより大きいです。")
a // 条件が成立するときに返す値
} else {
println("aはbより大きくありません。")
b // 条件が成立しないときに返す値
}
println(max)
}
Kotlinに「switch」文は存在しない。以下のように「when」文を使う
fun main() {
val a = 4
when (a) {
1 -> println("aは1です。")
2,3 -> println("aは2もしくは3です。")
else -> println("aはそれ以外の値です。")
}
}
■繰り返し
fun main() {
val numbers: Array<Int> = arrayOf(2, 4, 6, 8, 10)
// 配列の内容を処理(iには配列の値が入る)
for (i in numbers) {
println("[$i]")
}
// 配列の内容を処理(iには配列の添字が入る)
for (i in numbers.indices) {
println("[$i] = ${numbers[i]}")
}
// 範囲式を使った繰り返し
for (i in 1..4) {
println("[$i]")
}
for (i in 4 downTo 1) {
println("[$i]")
}
for (i in 0..9 step 2) {
println("[$i]")
}
// 条件を満たす間は繰り返し
var x = 0
while (x < 10) {
println("[$x]")
x++
}
// 処理を実行してから、条件を満たす間は繰り返し
var y = 7
do {
println("[$y]")
y--
} while (y > 4)
}
■コレクション
リストは配列と同じように、要素の順番を保持する
fun main() {
// 内容を変更できないリストを定義
val items: List<Int> = listOf(2, 4, 6)
println(items)
/*
// リストの内容を変更できない
items.add(8)
*/
}
fun main() {
// 内容を変更できるリストを定義
val items: MutableList<Int> = mutableListOf(2, 4, 6)
println(items)
// リストの内容を変更できる
items.add(8)
println(items)
}
セットは要素の順番を持たず、要素の重複もできない
fun main() {
// 内容を変更できないセットを定義
val items: Set<Int> = setOf(2, 4, 6)
println(items)
/*
// セットの内容を変更できない
items.add(8)
*/
}
fun main() {
// 内容を変更できるセットを定義
val items: MutableSet<Int> = mutableSetOf(2, 4, 6)
println(items)
// セットの内容を変更できる
items.add(8)
println(items)
}
マップはキーと値がついになった要素を持ち、要素の重複はできない
fun main() {
// 内容を変更できないマップを定義
val items: Map<String, Int> = mapOf("apple" to 1, "orange" to 2, "banana" to 3)
println(items)
/*
// マップの内容を変更できない
items.put("melon", 4)
*/
}
fun main() {
// 内容を変更できるマップを定義
val items: MutableMap<String, Int> = mutableMapOf("apple" to 1, "orange" to 2, "banana" to 3)
println(items)
// マップの内容を変更できる
items.put("melon", 4)
println(items)
}
■関数
関数の定義と実行例
fun main() {
val number = times(2, 5)
println(number)
}
fun times(a: Int, b: Int): Int {
return a * b
}
関数の戻り値が無い場合、戻り値の型に「Unit」を使う
(「: Unit」を省略することもできる)
fun main() {
hello("Hello!")
}
fun hello(message: String): Unit {
println(message)
}
引数には、デフォルト値を指定できる
また、呼び出す際に引数名を明示することができる
fun main() {
val number = calc(5, c = 4)
println(number)
}
fun calc(a: Int, b: Int=1, c: Int, d: Int=1): Int {
return a * b - c / d
}
■ラムダ式
関数に渡す式をラムダ式と呼び、ラムダ式を使うと関数を生成して他の関数の引数に渡すことができる
式は必ず「{ 〜 }」で囲う必要がある
処理に return 文は不要で、最後に評価された式が自動で返される
ラムダ式が代入される変数は、関数型となる
fun main() {
var times = { x: Int, y: Int -> x * y }
// 型を明示した場合は以下のようになる
/*
var times: (Int, Int) -> Int = { x: Int, y: Int -> x * y }
*/
println(times(2, 5))
}
引数が1つしかない場合、引数の指定と「->」の両方を省略できる
省略した引数は、暗黙の引数「it」として扱うことができる
fun main() {
var double: (Int) -> Int = { it * 2 }
// 型を明示した場合は以下のようになる
/*
var double: (Int) -> Int = { it * 2 }
var double: (Int) -> Int = { x: Int -> x * 2 }
*/
println(double(3))
}
関数型を引数に持つ関数も定義できる
fun main() {
println(doLambda(5, {it * 2}))
}
fun doLambda(x: Int, l: (Int) -> Int) = l(x)
■クラス
fun main() {
val dog = Dog()
dog.name = "Poch"
dog.hello()
println(dog.name)
}
class Dog {
var name: String = ""
fun hello() {
println("Hello, $name.")
}
}
コンストラクタを使うと、クラスからオブジェクトを作成した際に値を初期化できる
fun main() {
val dog = Dog("Poch")
// プロパティの値を変更できない
/*
dog.name = "Taro"
*/
dog.hello()
println(dog.name)
}
class Dog(val name: String) {
fun hello() {
println("Hello, $name.")
}
}
コンストラクタは処理を持たないので、値を初期化することしかできない
処理を行いたい場合はイニシャライザを使う
fun main() {
val dog = Dog("Poch")
dog.hello()
println(dog.name)
}
class Dog(val name: String) {
init {
println("name: $name")
}
fun hello() {
println("Hello, $name.")
}
}
クラスを継承すると、親クラスの機能を呼び出すことができる
Kotlinのクラスやプロパティはデフォルトでfinalになるため、継承させるにはopenキーワードを指定する必要がある
また親クラスから継承したプロパティやメソッドをサブクラス内で使用する場合、override修飾子を付ける必要がある
fun main() {
val shibaInu = ShibaInu("Poch")
shibaInu.hello()
shibaInu.bye()
println(shibaInu.name)
}
open class Dog(open val name: String) {
init {
println("name: $name")
}
fun hello() {
println("Hello, $name.")
}
open fun bye() {
println("Bye, $name.")
}
}
class ShibaInu(override val name: String): Dog(name) {
override fun bye() {
println("Bye, $name!")
}
}
インターフェイスの実装方法は、クラスの継承と同じ
メソッドをオーバーライドする場合、override修飾子を付ける必要がある
fun main() {
val cat = Cat()
cat.eat()
cat.showName()
}
interface Pet {
fun eat()
fun showName() = println("I'm pet")
}
class Cat: Pet {
override fun eat() = println("I'm eating")
}
■型チェックとスマートキャスト
AnyはNull非許容型のルートクラス
is演算子を使うことで、値の型をチェックできる
チェック後は明示的な型の変換(キャスト)が不要で、この機能を「スマートキャスト」と呼ぶ
fun main() {
val message = "Kotlin"
//val message = 10
var result: Int? = getLength(message)
println(result)
}
fun getLength(obj: Any): Int? {
if (obj is String) {
return obj.length
} else {
return null
}
}
■objectキーワード
object宣言を使用することで、シングルトンを実現できる。つまりクラスの宣言とインスタンスの生成を行える
(なおJavaにはシングルトンを実現する構文は無く、シングルトンになるように自分でプログラムを作成する必要がある)
fun main() {
println(Profile.getData())
}
object Profile {
var name: String = "Taro Yamada"
var age: Int = 24
fun getData(): String {
return "$name / $age"
}
}
companion宣言を使用することで、staticメソッドを実現できる
fun main() {
println(Person.showMe())
}
class Person(val name: String) {
companion object {
fun showMe(): String {
return "Hello."
}
}
}
以下のようにすると、main関数内で無名インナークラスを定義できる
これをオブジェクト式と呼ぶ
fun main() {
val x = object {
val num = 1
fun showMe(): String {
return "Hello."
}
}
println(x.num)
println(x.showMe())
}
■SAM変換
※まだよく理解できていないので、改めて考えたい
メソッドを1つしか持たないインターフェイスをSAM(Single Abstract Method)と呼び、
KotlinではSAMインターフェイスを引数としたメソッドを、ラムダ式で置き換えることができるようになっている
これをSAM変換と呼ぶ
object式を使って書かれたプログラムを例に挙げる
以下は「ボタンをタップした時にメッセージが表示される」というJavaのコード
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
TextView screen = findViewById(R.id.screenView);
screen.setText("ボタンがタップされました。");
}
});
Kotlinでは以下のように書くことができる
setOnClickListenerは引数にView.OnClickListenerインターフェイスを実装したクラスのインスタンスを取るので、
無名インナークラスを使って記述している
button.setOnClickListener {
object: View.OnClickListener {
override fun onClick(v: View?): Unit {
screen.text = "ボタンがタップされました。"
}
}
}
OnClickListenerインターフェイスはonClickしか持たないSAMインターフェイスのため、ラムダ式で書き換えることができる
onClickはタップされたViewを受け取り、voidを返すメソッドなので、ラムダの関数型は「(view)->Unit」となる
これにより、上のコードは以下のように簡略化できる
button.setOnClickListener ({
v -> screen.text = "ボタンがタップされました。"
})
ラムダ式は引数が1つしかなければ、省略して「it」で代用できる
button.setOnClickListener ({
screen.text = "ボタンがタップされました。"
})
また関数の最後の引数がラムダ式の場合、それを () の外へ出すことができる
button.setOnClickListener() {
screen.text = "ボタンがタップされました。"
}
さらにラムダ式が関数の唯一の引数の場合、() すらも省略できる
button.setOnClickListener {
screen.text = "ボタンがタップされました。"
}
最終的には、上記のような簡素な記述にすることができる
■null安全
オブジェクトの後ろに「?」を付けると、オブジェクトがnullならnullが返却される(安全呼び出し演算子)
fun main() {
var s: String? = null
//var s: String = "ABC"
print("The length of $s is ${s?.length}.")
}
以下のようにすると、nullの場合にデフォルト値を返すことができる(エルビス演算子)
fun main() {
val message = "Kotlin"
//val message = null
var result: Int = getLength(message)
println(result)
}
fun getLength(s: String?): Int {
return s?.length ?: 0
}
■null許容型のnullチェック
null許容型のオブジェクトの中にnullが入っていないことが確実な場合、そのオブジェクトはnull非許容型として扱える
fun main() {
val s: String? = "Kotlin"
if (s != null) {
println(s.length)
}
}
以下のように「!!」を使うことでも、強制的にnull非許容型として扱える
ただし値がnullであった場合はNullPointerExceptionのエラーが発生するので、使用は推奨されない
fun main() {
val s: String? = "Kotlin"
println(s!!.length)
}
■スコープ関数
fun main() {
// 通常の書き方
val dog = Dog()
dog.name = "Poch"
dog.hello()
// with関数を使用する
with (Dog()) {
// ラムダ式内でthisとして参照できるようになる
//this.name = "Poch"
//this.hello()
// さらにthisは省略可能なので以下のように書くことができる
name = "Poch"
hello()
}
// apply関数を使用する
Dog().apply {
// thisを省略可能なのはwith関数と同じ。さらにapply関数は戻り値が対象オブジェクトになる
name = "Poch"
}.hello()
// 通常の書き方(メソッドやプロパティの呼び出しに、毎回「?」を付ける必要がある)
val name: String? = "Kotlin"
var upper = name?.toUpperCase()
var len = name?.length
print("$upper $len")
// let関数を使用する
var output = name?.let {
// null許容型を扱いやすくする。対象オブジェクトはitで参照でき、ラムダ式の戻り値がletの戻り値になる
var upper = it.toUpperCase()
var len = it.length
"$upper $len"
}
print(output)
// run関数を使用する(withとletの両方の特性を持つ)
var output2 = name?.run {
// null許容型を扱いやすくする。thisも省略できる
"${toUpperCase()} $length"
}
print(output2)
// also関数を使用する(「対象オブジェクト『もまた』次のように処理して返す」の意味)
val arrayInt = listOf(2, 4, 6, 8).also {
// 対象オブジェクトはitで参照できる。ラムダ式の戻り値は対象オブジェクトになる
print(it)
}
}
class Dog {
var name: String = ""
fun hello() {
println("Hello, $name.")
}
}
■Pairクラス
2つの値を格納するためのシンプルなクラス
格納した値は、firstプロパティとsecondプロパティで参照できる
fun main() {
val p = Pair("apple", 1)
println("${p.first} is ${p.second}")
}
toを使った構文でPairを作ることもできる
これは、mapOfで値を初期化する際にも使用される
fun main() {
val p = "apple" to 1
println("${p.first} is ${p.second}")
}
■拡張関数
Javaでクラスに機能を追加する場合、クラス内にメソッドを追加するか、クラスを継承してメソッドを追加する必要がある
Kotlinでは、クラスの機能をクラスの外側で定義することができる
また関数内では、参照元クラスのインスタンスを「this」として扱う
fun main() {
println("Kotlin".surround())
}
fun String.surround() = "[" + this + "]"