iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
仕事でAndroidアプリケーションをKotlinで開発しているので、macOSでKotlinを試してみる。
KotlinといえばAndroidStudioでAndroidアプリケーションをプログラミングだと思うが、macOS上で動作するKotlinで書かれたプログラムを動かしたいので、開発環境としてIntelliJ IDEAを使うことにする。IntelliJ IDEAはUltimate EditionとCommunity Editionがあるか、Kotlinプログラミングの学ぶ目的なら、Community Editionで大丈夫だ。JetBrains社のWebサイト (https://www.jetbrains.com/ja-jp/idea/download/#section=mac) からダウンロードしよう。
IntelliJ IDEAが入手できたら、Java仮想マシンで動作するプログラムのプロジェクトを生成する。
この状態では、Kotlinのソースファイルは作られていないので、srcフォルダを右クリックして、New > Kotlin File/Class を選択して、Kotlinファイルを生成する。
そして、hello, worldを印字するコードを書く。
fun main(args: Array) {
println("hello, world")
}
Run.
hello, world と印字されている。
Javaでは結果を関数の戻る値で、エラー情報は例外で、だったが、Androidは例外を勧めていないことは以前から感じていた。Kotlinでは、例外は復旧できない致命的な状況で利用とし、既存のJavaモジュールが投げてくる例外を包み込みResult型があったりしている。
自分でKotlinでプログラミングする際、あえて例外を利用する必要はないと思うので、どんなやり方が合うのか調べてみて辿り着いたコードを紹介する。参考にしたのは、ここ。
AndroidはActivityやFragmentが再生成されるということから、ActivityやFragmentのメンバー変数でModelを持たないだとか、再生成されてもModelが破棄されない。ModelはActivityやFragmentをメンバー変数で持っていると再生されると破棄されたものにアクセスしてしまう問題がある。それの解決策としてJetpackのViewModelやLiveDataがあるのだが、LiveDataではエラーを例外で渡せない。結果とエラー情報がLiveDataとなっているのが扱いやすいということがあるので、結果とエラー情報を持つ型を用意することにした。
sealed class FindUserResult {
data class Found(val user: User) : FindUserResult()
data class NotFound(val name: String) : FindUserResult()
}
Swiftだとenumを利用すればだが、Kotlinのenum classは状態を定数で持つだけ。その代わり、sealed classを使えば値を持てる。そして、サブクラスを用意することによって、状態を持てる。
上の例では、FindUserResultのサブクラスがFoundとNotFoundになっていて 、プライマリコンストラクタでそれぞれのプロパティのuserとnameを宣言している。
この型を戻り値にした関数がこれ。
fun findUserByName(name: String): FindUserResult {
....
if (見つかった) return FindUserResult.Found(user)
else return FindUserResult.NotFound(name)
}
成功したらFoundを返している。失敗の場合はNotFoundを返している。処理の結果や、エラー情報はコンストラクタのパラメータで設定している。
この戻り値を受け取った側のコードがこれ。
val result = findUserByName("bitz")
when (result) {
is FindUserResult.Found -> println("find ${result.user}")
is FindUserResult.NotFound ->println("find ${result.name}")
}
型で成功か失敗が分かり、プロパティで結果やエラー情報が取れる。
Cocoa + Objective-C では、nilは許容されるものでnilに対してメソッド呼び出しを行なってもアボートしないが、Javaだとnullアクセスは例外が投げられてしまう。そのような考えの違いが、null安全についても、SwiftとKotlinで差となっているのかな?
JavaではC言語の基本的データ型に相当するのが基本型(プリミティブ型)で、C言語の構造体に相当するクラスはC言語のポインタ型に相当する参照型(リファレンス型)となる。そして、C言語と同様に値渡しのみとなる。
Kotlinでは全ての型はオブジェクトで、それは参照型(リファレンス型)となる。参照型だとJavaではnullを代入できるが、Kotlinではnullが代入できるnull許容(nullable)と、null非許容(non-nullable)がある。
null許容は型名に?を付ける。
var a: String? = null
null非許容は型名に?を付けない。以下はコンパイルでエラーとなる。
var a: String = null
null許容の変数は、nullの可能性があるので、そのまま利用するとコンパイルでエラーとなる。
fun main(args: Array) {
var s: String? = "demo"
var n = s.length
print(n)
}
Error:(3, 14) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
セーフコール演算子(?.)を使うと、nullでない場合は評価され、nullだと回避される。
var s: String? = "demo"
var n = s?.length
print(n)
セーフコール演算子を使う場合は、nullでないことの確認を行うことになると思うが、素直に書くと以下のようになる。
var s: String? = "demo"
if (s != null) {
var n = s?.length
print(n)
}
もっとスマートに書くために、セーフコール演算子と一緒に使えるlet関数が用意されている。
var s: String? = null
s?.let {
var n = it.length
print(n)
}
デバッグなので強制的にnullでないものとして使うための非null表明演算子(non-null assertion operator)、または、二重感嘆符演算子(double-bang operator)と呼ばれる演算子(!!.)がある。
var s: String? = "demo"
var n = s!!.length
print(n)
別のnullチェックのやり方として、null合体演算子(null coalescing operator)、または、エルヴィス演算子と呼ばれる演算子(?:)がある。
var s: String? = null
var t = s ?: ""
var n = t!!.length
print(n)
let関数とnull合体演算子を組み合わせると、以下のように書ける。
var s: String? = null
s?.let {
var n = it.length
print(n)
} ?: print("s is null.")
Kotlinで特徴的なのは、文法的にnullチェックされているのが分かっている場合は、コンパイラをnullチェック済みとするところだ。
var s: String? = "demo"
if (s == null) {
print("s is null")
return
}
var n = s.length
print(n)
これは開発環境を提供している会社が作ったプログラミング言語だからなせる技か。Kotlinには似たような割り切りを感じる部分が他にもある。機会があれば後で取りあげたい。