#はじめに
Kotlinを勉強し始めて1ヶ月ほど経過しました。
Kotlinも他の言語と共通する文法もあり、他の言語を知っていれば1から文法を勉強しなくてもなんとなく読めます。
そんな中でも僕が感じたKotlin特有というかKotlinぽい?というか、そんな記述をまとめてみました。
Kotlinを勉強し始める方は、勉強する前に前にさらっと目を通していただいて雰囲気をつかんでいただけますと幸いです。
#型の後置修飾
var i : Int
val d : Double
val s : String
fun Area(height : Int, width : Int) : Int {return height * width}
このように整数や浮動小数点、文字列などの変数の方を後置修飾します。また関数の返り値の型も関数名(引数)の後ろに置きます。
変数には大きく分けてvar変数とval変数があります。val変数は再代入不可、var変数は再代入可です。ごっちゃになりやすいですね。。。基本的には再代入不可のval変数でコードを書いた方がバグの混入が減らせそうです。
#if
Kotlinのifは文ではなく式です。なのでifを用いて以下のような記述ができます。
fun checkOddNumber(value: Int): Boolean{
val res: Boolean = if(value % 2 == 0) true else false
return res
}
式なので、これを展開して直接returnすることができます。Kotlinには三項演算子はなく、このようにif式を代替します。
fun checkOddNumber(value: Int): Boolean{
return if(value % 2 == 0) true else false
}
もちろん文っぽくifを用いることもできます。
fun checkOddNumber(value: Int): Boolean{
if(value % 2 == 0) {
res = true
} else {
res = false
}
return res
}
#when
CやJavaのswitch-case文のような機能です。こちらも式として変数に代入できますし、文として書くこともできます。
val kisetu: String = "夏"
val season: String
season = when(kisetu){
"春" -> "Spring"
"夏" -> "Summer"
"秋" -> "Fall"
"冬" -> "Winter"
else -> "Unknown"
}
println(season)
//Summer
val kisetu: String= "冬"
when(kisetu){
"春" -> {println("Spring")}
"夏" -> {println("Summer")}
"秋" -> {println("Fall")}
"冬" -> {println("Winter")}
else -> {println("Unknown")}
}
//Winter
#null安全
Javaではnullの変数に対するメンバ変数/関数にアクセスしようとするとNullPointerExceptionが発生します。Kotlinでは基本的にnullを許容しないので、未然にNullPointerExceptionを防ぐことができます。コンパイル時にnullの変数が存在すればコンパイルエラーとなります。
var str : String
str = null
//error: null can not be a value of a non-null type String
初期化されていない変数や変数のメソッドやメンバへのアクセスもコンパイルエラーとなります。
val members : Members
members.findByName("Bob")
...
class Members{
...
fun findByName(...){...}
}
//error: variable 'members' must be initialized
通常、変数は宣言時に初期化します。
val str: String = "Hello world"
println(str)
//OK
一方、nullを許容する変数も存在します。型名?
でnull許容型変数となります。null許容型変数をnullで初期化していれば、error variable 'xxx' must be initialized
のエラーは出ません。以下のコードはコンパイルは正常に終了します。
var str: String?
str = null
//OK
以下のコードで変数のメソッドやメンバを呼ぶ前にnullチェックを行ってくれます。もしnullなら無条件に"null"
の文字列を返します。
var str: String?
str = null
println(str?.length) //"null"
//OK
nullと未初期化状態は区別されます。そのため、null許容型と言っても以下のコードはエラーとなります。
var str: String?
println(str?.length)
//error: variable 'str' must be initialized
コンパイラがちょっと厳しいなあと感じることが多々あります。そのぶん出来上がりのコードの安全性はそれなりのものになってると信じたい!
#配列、リスト、マップ
配列、リスト、マップの宣言の仕方は以下の通りです。
初期化を行うにはxxxOf()
を用います。こちらは読み取りのみできる配列、リスト、マップです。マップに関してはkey to value
の記述で、マップのkeyとvalueを登録します。読み取りのみのものは最初の初期化は値を追加したり、削除することはできません。MutableXxx
, mutableXxxOf
で読み書きができる配列、リスト、マップの宣言と初期化を行うことができます。
//読み取りのみ
var array : Array<String> = arrayOf("春", "夏", "秋", "冬")
var list : List<String> = listOf("春", "夏", "秋", "冬")
var map : Map<String, String> = mapOf("春" to "Spring", "夏" to "Summer")
//要素の追加削除変更ができる
var mutalearray : MutableArray<String> = mutableArrayOf(...)
var mutablelist : MutableList<String> = mutableArrayOf(...)
var mutablemap : MutableMap<String, Int> = mutableArrayOf(...)
(コメントから追記)key to value
に関して、これは言語で定められた構文ではなく、Pair クラスのインスタンスを生成する to
関数 の呼び出しです。 Kotlin では二項演算子のように使える中置関数を定義することができます。
例えば以下のように宣言しておいて、リストに値を追加しようとしてもコンパイルエラーとなります。
var season : MutableList<String>
season.add("春")
//variable 'season' must be initialized
season
には初期化しない限りnullだからです。初期値は入れないけど、後で値を入れる想定なら、mutableListOf
で空のリストで初期化します。
var season : MutableList<String> = mutableListOf()
season.add("春")
#for
とforEach
kotlinではforで指定数の繰り返し処理や、配列やリスト、マップの各要素に任意の処理をすることができます。
val season : Map<String, String> = mapOf("春" to "Spring", "夏" to "Summer", "秋" to "Fall", "冬" to "Winter")
for (elem in season){
println("${elem.key} ${elem.value}")
}
var sum : Int = 0
for (i in 1..10){
sum += i
}
//iを2ずつ加算してループを実行
var odd_sum : Int = 0
for (i in 1..10 step 2){
odd_sum += i
}
forEachは配列やリスト、マップのメソッドで、ほぼforと同じことができます。
val season : Map<String, String> = mapOf("春" to "Spring", "夏" to "Summer", "秋" to "Fall", "冬" to "Winter")
season.forEach({
elem -> println("${it.key} ${it.value}")
})
forEachでは各要素に対して処理したい内容の関数を引数にとります。このように引数に関数をとる関数を高階関数と呼びます。また引数の関数はラムダ式で記述されることが多いです。
#型推論
Kotlinでは変数の型を明示しなくても、初期化する値によって型を自動で定義してくれます。
val num1 = 10 //Int
val num2 = 9.8 //Double
val str = "Hello" //String
val season = mapOf("春" to "Spring", "夏" to "Summer", "秋" to "Fall", "冬" to "Winter") //Map<String, String>
静的型付けの恩恵を受けつつ、コードもすっきりします。
#ラムダ式と高階関数
試しに引数で当てられた関数の実行時間を計測する高階関数(measureTime)を作ってみます。
//UnitはC言語でいうvoid型みたいなもの
fun measureTime(targetFunction: (Int) -> Unit) : Long{
val start = System.currentTimeMillis()
targetFunction(1000000)
val end = System.currentTimeMillis()
return end - start
}
fun measureTime(targetFunction: (Int) -> Unit) : Long
この部分、関数引数の指定の仕方は、関数名 : (引数, ...) -> 返り値の型
です。
計測する関数は以下の関数とします。
fun target(loop){
var x = 0
for(i in 1..loop) {x++}
}
この関数を引数に入れます。
関数へは::
を付けることで参照できます。
var time : Int = measureTime(::target)
println(time)
//3(ex)
このtarget
関数はラムダ式(無名関数)で記述できます。
var time : Long = measureTime({loop : Int ->
var x = 0
for(i in 1..loop) {x++}
})
println(time)
//3(ex)
{引数,... -> 処理内容}
でラムダ式を記述します。関数の最後の引数がラムダ式の場合、ラムダ式は関数の()の外に出せます。この例では引数がラムダ式のみなので、ラムダ式が最初の引数であって最後の引数です。()には残りの変数の引数が入り得ます。
var time : Long = measureTime(){loop : Int ->
var x = 0
for(i in 1..loop) {x++}
}
また関数の引数がラムダ式だけの場合、関数の()も省略できます。結局引数がラムダ式1つだけの場合は以下のように()を省略できます。
var time : Long = measureTime{loop : Int ->
var x = 0
for(i in 1..loop) {x++}
}
同様にしてforEachも()を省略できます。
season.forEach{ elem ->
println("${elem.key} ${elem.value}")
}
(コメントを受けて)暗黙的に仮引数が1つしかないラムダ式の引数はit
として定義されます。forEachでは各要素は変数it
に入れられ、以下のように書けます。
season.forEach{
println("${it.key} ${it.value}")
}
すっきりしましたね!
#最後に
Kotlinの特徴的な文法などをまとめてみました。
今回は基礎部分だけだったのでこれの続編もそのうち書こうと思います。
コメントくださった方ありがとうございます!
#参考文献
速習Kotlin(Kindle) 著:山田祥寛