はじめに
1.1 対象読者
- 対象:Swift または Kotlin のどちらかに慣れていて、もう一方へ“脳内スイッチ”したい iOS/Android/共通化エンジニア
- ゴール:型・宣言・非同期・エラー・ADT を最短経路で置き換えられるようにする
※ ADT = 抽象データ型 (Abstract Data Type、クラスとか構造体を指す)
1.2 本記事の読み方
- 2章の対応表で“あの書き方は Kotlin/Swift だと何?”を一気に変換
- その後、各章のサンプルを流し読み → 実務コードへコピペ&調整
クイック対応表
2.1 基本型マッピング(数値・文字列・真偽・コレクション)
| 概念 | Swift | Kotlin | 備考 |
|---|---|---|---|
| 整数 | Int(64bit相当), UInt | Int(32bit), Long(64bit) | ビット幅に注意 |
| 実数 | Double, Float | Double, Float | 既定は両者とも Double が多い |
| 真偽 | Bool | Boolean | — |
| 文字 | Character | Char | — |
| 文字列 | String | String | — |
| 配列 | [T] / Array<T> | List<T> / MutableList<T> | Swift配列は値型、Kotlinはインターフェース+実装 |
| 辞書 | [K: V] / Dictionary | Map<K, V> / MutableMap<K, V> | — |
| 集合 | Set<T> | Set<T> / MutableSet<T> | — |
| バイト列 | Data | ByteArray | — |
| 日付 | Date | Instant/LocalDateTime など | Java Time API を利用 |
| 任意型 | Any, AnyObject | Any | Kotlin の Any? は null 許容 |
コレクションの生成例:
// Swift
let list: [Int] = [1, 2, 3]
let dict: [String: Int] = ["a": 1]
var set: Set<String> = ["a", "b"]
// Kotlin
val list: List<Int> = listOf(1, 2, 3)
val dict: Map<String, Int> = mapOf("a" to 1)
val set: MutableSet<String> = mutableSetOf("a", "b")2.2 値/参照・不変/可変(struct/class vs data class/class, let/var vs val/var)
Swift:
structは値型、classは参照型。変数束縛はlet(不変)/var(可変)。let user = User(name: "A")ならuserは再代入不可。structのプロパティはuserがletだと全体的に不変。
Kotlin:
data class/classは参照型。フィールドの不変はval(読み取り専用)/var(可変)で表現。data classはequals/hashCode/copyを自動生成。
例:
// Swift
struct User {
var name: String
}
var user1 = User(name: "Alice")
var user2 = user1
user2.name = "Bob" // user1のnameは変わらない
// Kotlin
data class User(
var name: String
)
val user1 = User("Alice")
val user2 = user1.copy(name = "Bob") // user1は変わらない2.3 Null安全とオプショナル(T?/if let/guard vs T?/?./?:/!!)
Swift:
T?が Optional。if let/guard letでアンラップ。?.(チェーン)、??(nil結合)。
Kotlin:
T?が Nullable。?.(セーフコール)、?:(Elvis)、!!(非推奨・実務では極力使わない)。
例:
// Swift
func greet(_ name: String?) -> String {
guard let name else { return "Hi, guest" }
return "Hi, \(name)"
}
let length = name?.count ?? 0
// Kotlin
fun greet(name: String?): String =
name?.let { "Hi, $it" } ?: "Hi, guest"
val length = name?.length ?: 02.4 列挙と代数的データ型(enum + associated values ↔ sealed class/interface)
Swift:
- 関連値付き
enumで ADT を自然に表現。switchは網羅性チェックあり。
Kotlin:
sealed class/sealed interfaceとdata class/objectの組合せで ADT。whenで網羅性チェック(else不要にできる)。
例:
// Swift
enum Result<T> {
case success(T)
case failure(Error)
}
func handle(_ r: Result<Int>) {
switch r {
case .success(let value):
print(value)
case .failure(let error):
print(error.localizedDescription)
}
}
// Kotlin
sealed interface Result<out T> {
data class Success<T>(val value: T): Result<T>
data class Failure(val error: Throwable): Result<Nothing>
}
fun handle(r: Result<Int>) = when (r) {
is Result.Success -> println(r.value)
is Result.Failure -> println(r.error.message)
}2.5 エラーハンドリング(throws/try ↔ try/catch/Result)
Swift:
- 投げる関数は
throws、呼び出しはtry。try?でOptionalに変換、try!はクラッシュ要因。do-catchで捕捉。
Kotlin:
- チェック例外は存在せず、
try/catchで捕捉。関数型合成や戻り値で扱いたい時はResult<T>/runCatchingを使う。
例:
// Swift
enum MyError: Error {
case oops
}
func risky() throws -> Int {
throw MyError.oops
}
let a: Int? = try? risky() // nil
do {
_ = try risky()
} catch {
print(error)
}
// Kotlin
class MyError: RuntimeException("oops")
fun risky(): Int { throw MyError() }
val a: Result<Int> = runCatching { risky() }
a.getOrElse { 0 } // 0
try {
risky()
} catch (e: MyError) {
println(e)
}2.6 非同期処理(async/await/Task ↔ suspend/Coroutines/Flow)
Swift:
- 構造化並行性(
async/await、Task、async let、TaskGroup、Actor)。キャンセルは協調的。
Kotlin:
suspend関数+Coroutines(CoroutineScope/launch/async/withContext)とFlow。同じく協調的キャンセル。
例:
// Swift
func fetchA() async throws -> Int { 1 }
func fetchB() async throws -> Int { 2 }
func fetchBoth() async throws -> Int {
async let a = fetchA()
async let b = fetchB()
return try await a + b // 3
}
// Kotlin
suspend fun fetchA(): Int = 1
suspend fun fetchB(): Int = 2
suspend fun fetchBoth(): Int = coroutineScope {
val a = async { fetchA() }
val b = async { fetchB() }
a.await() + b.await()
}2.7 拡張と合成(Swift の extension/プロトコル指向 ↔ Kotlin の拡張関数/インターフェース)
Swift:
extensionで後付けメソッド/プロトコル適合。プロトコル+デフォルト実装で合成。
Kotlin:
- 拡張関数/プロパティで後付け API。
interfaceにデフォルト実装も可。両者とも静的ディスパッチ(実行時オーバーライドではない)に注意。
例:
// Swift
protocol Printable {
func text() -> String
}
extension Int: Printable {
func text() -> String { "\(self)" } // selfは、Int型
}
// Printableに準拠している要素を持つCollectionに対する拡張
extension Collection where Element: Printable {
func joinedText() -> String { map { $0.text() }.joined(separator: ",") }
}
// Kotlin
interface Printable {
fun text(): String
}
fun Int.text(): String = "$this"
fun <T: Printable> Collection<T>.joinedText(): String =
this.joinToString(",") {
it.text()
}
