Swift - Kotlin 言語比較 その1
Swift - Kotlin 言語比較 その1
はじめに
こんにちは、株式会社アイスタイルで@cosmeアプリのiOSエンジニアをしている上野初仁(うえのはつひと)と申します。
アイスタイルには外部委託として参画しており、今回はこのようなテックブログを書く機会をいただきました。参画前はDartによるアプリ開発や、サーバサイドKotlinを使ったシステムの構築などをしていましたが、SwiftおよびKotlinによるスマホアプリ開発は参画して初めての経験となります。
今回はそういった自分の経験も踏まえ、アプリ開発で良く用いられている2つの言語(Swift, Kotlin)について、それぞれの言語の言語比較をしてみます。
そもそも Swift, Kotlin とは
Swift
公式ページ :
SwiftとはApple社が開発した言語で、iOSやmacOSなどのアプリ開発のための言語です。元々使われていたObjective-Cに比べわかりやすい言語体系となっています。
その後オープンソース化され、macOS, Linux, WindowsのそれぞれのOS上で動作する言語セットのバイナリが提供されています。
「わかりやすい」と書いていますが言語としてのわかりやすさと、アプリ開発での使いやすさ・わかりやすさは別物で、アプリ開発自体はとっつきにくい印象が私にはあります。これはObjective-Cとの相互運用可能ゆえの難しさや、UIKitやSwiftUIといったUI周りの仕様の複雑さや落とし穴にあるように考えます。そのため、この記事ではUI周りの実装については記載をせず、あくまで言語仕様に絞って記載していきます。
Kotlin
公式ページ :
KotlinとはJetBrains社が開発した言語で、Java言語の仕様と思想を元に、さらに使いやすい言語体系となるように作られた言語です。Javaとの相互運用ができる作りになっており、Java開発経験がある開発者ならスムーズに移行でき、初学者にもわかりやすい言語体系となっています。
オープンソースの元で開発されており、色々なプラットフォームでの開発や実行が可能です。Java仮想マシンなどの話はオーバースペック気味なこともあり、ここでは割愛しますが、スマホアプリだけでなく、サーバサイドのシステムでも利用が広がっています。
JavaとくらべNull Safetyになっている点が私は一番の利点だと思っています。他にも独特な非同期処理への対応などあり、個人的な印象としてはこの2つの言語の中で一番に習得したい言語・習得をオススメしたい言語となっています。
実際の言語比較
インクリメント・デクリメント
Swiftには通常の言語でいうところの インクリメント
や デクリメント
がありません。代わりに代入演算子を使って += 1
や -= 1
します。
// Swift
var n = 1
n += 1 // n = 2
n -= 1 // n = 1
// Kotlin
var n = 1
n++ // n = 2 ← Kotlinではインクリメントが可能
n-- // n = 1 ← Kotlinではデクリメントが可能
n += 1 // n = 2 ← もちろん加算代入も可能
n -= 1 // n = 1 ← もちろん減算代入も可能
var, let, val
変数や定数の宣言には var
、let
、val
などを使います。
変数宣言にはどちらの言語もvar
を、定数宣言にはSwiftはlet
、Kotlinはval
を使います。
var
はVariable、let
は英語のLet、val
はValueから名付けられています。プログラミング言語でLET
キーワードが使われたのは古くBASICやLispに遡れます。今ではJavaScriptなどでも利用されています。英語で「xを1とする」と表すとLet x be 1.
となり、ここから引用されたのですね。
今回は変数宣言
と定数宣言
と便宜上書いています。実際にはSwiftでは変数
、定数
と呼べますが、Kotlinの公式の記述に沿うとミュータブルな変数
、リードオンリーな変数
となります。
Swift | Kotlin | |
---|---|---|
変数宣言 | var (Variables) | var (Mutable Variables) |
定数宣言 | let (Constants) | val (Read-Only Variables) |
// Swift
var someVariable = 1 // ← 変数に1を設定
someVariable = 2 // ← 変数を2に変更 (OK)
let someConstant = 1 // ← 定数に1を設定
someConstant = 2 // ← 定数を2に変更 (NG) コンパイルできない
// Kotlin
var someMutableVariable = 1 // ← ミュータブルな変数に1を設定
someMutableVariable = 2 // ← ミュータブルな変数を2に変更 (OK)
val someReadOnlyVariable = 1 // ← リードオンリーな変数に1を設定
someReadOnlyVariable = 2 // ← リードオンリーな変数を2に変更 (NG) コンパイルできない
ここまでであれば「変数」「定数」とどちらも呼べそうなのですが、それが正確ではない理由を以下にまとめます。
ミュータブルな変数
、リードオンリーな変数
とは
Kotlin公式の書き方ミュータブルな変数 (Mutable Variables)
、リードオンリーな変数 (Read-Only Variables)
、ちょっとわかりづらいですね。まずはミュータブル
とは何でしょうか。
ミュータブルとは
ミュータブルの反対語としてイミュータブルがあります。以下表にまとめます。
ミュータブル | イミュータブル | |
---|---|---|
日本語訳 | 変更可能 | 変更不可能 |
性格 | 一旦設定した値を変更可能 | 一旦設定した値は変更不可能 |
では、Kotlinのval
はイミュータブルな変数を示すかというとそうではなく、厳密にはリードオンリー(読み込みのみ)の変数という扱いになります。なぜでしょうか。
ミュータブルなリスト
とイミュータブルなリスト
まず先に説明のために2つのリストについて紹介します。それがミュータブルなリスト
とイミュータブルなリスト
です。
以下の2つの変数mutableList
とimmutableList
はそれぞれ、ミュータブルとイミュータブルなリスト(Kotlinでいうところの配列)です。
変更可能か不可能かは宣言の時点で異なります。また注意していただきたいのは、どちらもval
で宣言している点です。ミュータブル・イミュータブルとval
・var
は関係ないことがわかります。
// Kotlin
val mutableList = mutableListOf("a", "b", "c")
val immutableList = listOf("a", "b", "c")
val
なのに変更可能(ミュータブル)、var
なのに変更不可能(イミュータブル)
では実際にいくつかの例を示します。
リストに要素を追加するadd()
を利用します。
下記のようにミュータブルなリストはval
で宣言しても要素の追加が可能です。
// Kotlin
val mutableList = mutableListOf("a", "b", "c") // ← ミュータブルなリストとして宣言
mutableList.add("d") // ← valなのに要素の追加可能
イミュータブルなリストにはそもそもadd()
が宣言されていないため要素の追加ができません。
変更についても関数が宣言されていません。
// Kotlin
val immutableList = listOf("a", "b", "c")
immutableList.add("d") // ← Error: イミュータブルなリストにはそもそもadd()が存在しないためコンパイルできない
一方イミュータブルなリストでもvar
で宣言されていたら書き込み可能なためリスト自体の上書きは可能です。もしval
で宣言していたらリードオンリーの変数を変更したためコンパイルエラーとなってしまいます。
// Kotlin
var writableList = listOf("a", "b", "c") // ← 書き込み可能なイミュータッブルなリストとして宣言
writableList = listOf("d", "e", "f") // ← 別のリストで上書き可能
val nonWritableList = listOf("a", "b", "c") // ← 書き込み不可能なイミュータッブルなリストとして宣言
nonWritableList = listOf("d", "e", "f") // ← ERROR: 別のリストで上書き不可能、コンパイルエラー
一方Swiftでは
一方Swiftでは、配列(Array)に要素を追加する関数append()
が用意されています。その動作をvar
とlet
でみてみましょう。
var
で宣言したリストにはappend()
が正しく動作し要素追加されます。
// Swift
var variableList = ["a", "b", "c"]
variableList.append("d") // ← (OK) variableList = ["a", "b", "c", "d"]
let
で宣言したリストではappend()
は利用できずコンパイルエラーとなってしまいます。
// Swift
let constantList = ["a", "b", "c"]
constantList.append("d") // ← (NG) 定数のリストは変更できないためコンパイルできない
これらからもわかるように、Swiftでは宣言時に「変更可能か、不可能か」を指定するのにvar
とlet
を利用します。
SwiftとKotlin、かなり似ていますがこういったところで大きな差が出てきます。
SwiftとKotlinのArrayの違い
Swiftでは配列はArrayですが、Kotlinでは先に示したListとは別にArrayがあります。
Swiftでは先に見た通り、var
で宣言するかlet
で宣言するかで要素の追加変更が可能かが決まります。
一方、Kotlinは、mutableListが要素の追加が可能なのに対し、Arrayは要素固定の配列となり要素の追加削除ができません。
その代わり、要素の変更は可能です。
// Kotlin
val someArray = Array(3) { 0 } // ← (0, 0, 0)
someArray[0] = 1 // (1, 0, 0)
ArrayはmutableListと同様にval
で宣言していても要素の変更可能なこと(ミュータブルであること)がわかります。
関数
SwiftとKotlinで関数の定義の仕方は若干の違いとなります。
// Swift
func someFunction(param: Int = 1) -> Bool {
return param == 1
}
// Kotlin
fun someFunction(param: Int = 1): Boolean {
return param == 1
}
- Swiftは関数定義に
func
を、Kotlinはfun
を使う - 戻り値は関数名のカッコの後にSwiftでは
-> 戻り値型
、Kotlinでは: 戻り値型
を使う
nil
or null
SwiftではOptionalを表すのにnil
を、KotlinではNullを表すのにnull
を使います。
OptionalとはSwiftのNullable(NULL許容)属性のことで、一般的な言語のNullと同じと考えて構いません。
SwiftでもObjective-Cのライブラリを利用する際などにNULLやNSNullが出てきますが、ここでは「あるよ」程度に説明を留めておきます。
Null安全
Swift、KotlinともにNull安全性をもった言語です。ではどのようにNull安全を担保するかコードで示してみます。
以下(1)では、それぞれNull許容型の値をNull非許容型の値に変換しています。SwiftではNil結合演算子??
によって左項がOptionalだった場合は、右項の値(ここではdefault string
)が設定されます。
同様にKotlinではエルビス演算子?:
の左項、右項が用いられます。エルビス演算子とは?:
がエルビスプレスリーの顔に見えるから名付けられたそうです。ちょっとかわいいですね。
// Swift
// (1)
let someOptional: String? = nil
let someNonOptional = someOptional ?? "default string"
// Kotlin
// (1)
val someNullable: String? = null
val someNonNullable = someNullable ?: "default string"
(2)ではそれぞれOptionalかNullかの判定をif文でして条件分岐しています。
Swiftはif let文
を使うことでOptionalの値を一時的に非Optionalの値にバインドして利用できます。
Kotlinはif != null文
を使うと、そのif文の中では非Nullであることが担保されます。
// Swift
// (2)
let someOptional: String? = nil
if let someNonOptional = someOptional {
print(someNonOptional)
} else {
print("default string")
}
// Kotlin
// (2)
val someNullable: String? = null
if (someNullable != null) {
println(someNullable)
} else {
println("default string")
}
(3)は(1)、(2)とちょっと違った方法でOptional, Nullを回避しています。
Swiftはguard let else文
を使い、Optionalの場合はelse
の中が実行され、非Optionalの場合は非Optionalであることがelseの外で担保されます。ただしreturn
が書かれていることからもわかるように、関数内で使うことになります。
Kotlinはlet
節を使って非Nullを担保しています。このSwiftとKotlinのコードは必ずしも同じ動作をしませんが、説明のためにあえて作成したコードとなっていますのでご了承ください。
// Swift
// (3)
let someOptional: String? = nil
guard let someNonOptional = someOptional else {
print("default string")
return
}
print(someNonOptional)
// Kotlin
// (3)
val someNullable: String? = null
val someNonNullable = someNullable.let { nonNull -> nonNull } ?: "default string"
println(someNonNullable)
let
KotlinのKotlinでlet
が出てきたので簡単に説明します。Kotlinではlet
節を使うと、その節に与えた値がNullでない場合に{}
の中に処理が入ります。
(1)はNullでない値をそのまま利用しているため、値はit
にバインドされます。
// Kotlin
// (1)
val someNonNullable: String = "test"
someNonNullable.let {
println(it) // ← someNonNullableがNullでない場合、Nullでない値は it にバインドされ println() される
}
(2)ではnonNull
にバインドさせているため、it
ではなくnonNull
を使ってprintln()
しています。
// Kotlin
// (2)
val someNonNullable: String = "test"
someNonNullable.let { nonNull -> // ← このように書くと it ではなく指定した値(ここでは nonNull)にバインドされる
println(nonNull) // ← Nullでない値が表示される
}
if文
Swiftではif文につきもののカッコが必要ありません。また&&
の代わりに,
で区切っても良いことになっています。
// Swift
if a == b, b == c {
print("OK")
} else {
print("NG")
}
// Kotlin
if (a == b && b == c) {
println("OK")
} else {
println("NG")
}
switch case
とwhen
Swiftには他言語と同様switch case
が、Kotlinにはwhen
があります。
// Swift
switch n {
case 1:
fallthrough
case 2:
print("1 or 2")
case 3:
print("3")
default:
print("other")
}
// Kotlin
when (n) {
1, 2 -> println("1 or 2")
3 -> println("3")
else -> println("other")
}
他言語のswitch case
にはcase
ごとにbreak
が必要でしたが、Swiftでは必要ありません。
その代わり例のように1と2をどちらも動かすにはfallthrough
を使います。
一方Kotlinではwhen
を使い例のように使います。
チェックする値がEnum型だった場合、Swift・KotlinともEnumを網羅するようにしないとコンパイルエラーが出ます。
これにより不要なバグの混入などを防ぐことができます。
if
やwhen
KotlinのKotlinのif
やwhen
は式のため、その結果を値に代入可能です。
// Kotlin
// (1)
val a = 1
val someValue1 = if (a == 1) "OK" else "NG" // ← someValue1 = "OK"
// (2)
val n = 3
val someValue2 = when (n) {
1 -> "one"
2 -> "two"
else -> "other"
} // ← someValue2 = "other"
私はKotlinのこの機能、もっと他の言語に波及すればよいのにと考えています。と書いていたら、この原稿のレビューで「Swiftも書けるようになるよ」とツッコミが。Swift 5.9になるとif
は式として認識されるそうです(やったね)。
おわりに
今回基本的な部分の言語仕様の差についてまとめました。今後続編でfilter
やmap
、クラスや構造体など応用的な部分について説明できればと考えています。
今後、Compose Multiplatformの普及などでSwift話者がKotlinを触る機会も増えてくるのではと考えています。その一助になれば幸いです。
アイスタイルでは@cosmeアプリを一緒に開発してくれるメンバーを募集中です。一緒により一層すばらしいプロダクトにしていきませんか。ご応募をお待ちしています。
iOSエンジニア
Androidエンジニア
Discussion