前回の記事はこちら:

Contents 非表示
宣言方法の違い(文法スニペット対比)
3.1 変数・定数(型注釈・型推論・初期化)
Swift:
let
=定数、var
=変数。型は: 型名
Kotlin:
val
=定数、var
=変数。型は: 型名
※ どちらも型推論あり。
例:
// Swift
let count: Int = 3 // 型注釈
var name = "Alice" // 型推論 (String)
var scores: [Int] = [] // 空配列は注釈入れておくと安心
// Kotlin
val count: Int = 3 // 型注釈
var name = "Alice" // 型推論 (String)
var scores: MutableList<Int> = mutableListOf()
※ 空のコレクションは型注釈を付けるとエラーになりにくい。
3.2 関数宣言(デフォルト引数・名前付き引数・可変長)
Swift:
- 引数ラベル(外部名)で自然に名前指定。
_
でラベル省略。 - 可変長(varargs):
Int...
Kotlin:
- 呼び出し側で
name = 値
と書く。 - 可変長(varargs):
vararg Int
※ デフォルト引数:両方とも 使える。
例:
// Swift
func greet(to person: String = "Guest", times: Int = 1) {
for _ in 0..<times { print("Hi, \(person)") }
}
greet(to: "Alice", times: 2) // ラベル必須
greet(times: 3) // person はデフォルト
greet(to: "Bob") // times はデフォルト
func sum(_ numbers: Int...) -> Int { numbers.reduce(0, +) }
sum(1, 2, 3) // 6
// Kotlin
fun greet(person: String = "Guest", times: Int = 1) {
repeat(times) { println("Hi, $person") }
}
greet(person = "Alice", times = 2) // 名前付き
greet(times = 3) // person はデフォルト
greet("Bob") // 位置引数でもOK
fun sum(vararg numbers: Int): Int = numbers.sum()
sum(1, 2, 3) // 6
3.3 クラス/構造体/データクラス(イニシャライザ/コンストラクタ、copy
/memberwise init)
Swift:
struct
(値型)には初期化が自動付与。class
(参照型)はinit
が基本。
Kotlin:
data class
は主コンストラクタでプロパティ宣言+copy
が自動。class
は必要に応じてプロパティとconstructor
。
例:
// Swift (Struct:init 自動生成)
struct User {
var name: String
var age: Int = 0
}
let user1 = User(name: "Alice") // age はデフォルト 0
let user2 = User(name: "Bob", age: 20) // もう一つのパターン
// Swift(class:init 必要)
class Person {
let name: String
init(name: String) {
self.name = name
}
}
// Kotlin(data class:copy 付き)
data class User(
val name: String,
val age: Int = 0
)
val user1 = User("Alice")
val user2 = User("Bob", 20)
val user3 = user2.copy(age = 21) // 一部だけ差し替えて新インスタンス
※ Swift struct
は代入で丸ごとコピーされるイメージ。Kotlin data class
は copy
で差分を作る。
3.4 プロパティ(計算型・遅延・バックフィールド/willSet
/didSet
vs get()/set()
)
Swift:
- 計算プロパティ:
var value: Int { get {…} set {…} }
(get
だけなら省略可) - 遅延:
lazy var
(参照型で使いやすい) - 変更監視:
willSet
/didSet
Kotlin:
- 計算プロパティ:
val/var
にget()
/set(value)
を付ける - 遅延:
by lazy { ... }
(スレッドセーフ既定)、lateinit var
(後初期化用、プリミティブ不可) - 変更監視:カスタム
set(value) { … }
内でfield
(バックフィールド)を操作
例:
// Swift
struct Rect {
var width: Double
var height: Double
var area: Double { width * height } // 計算プロパティ
lazy var cache = [String: Any]() // 初アクセスで生成
var name: String = "rect" {
willSet { print("will set to \(newValue)") } // 呼ばれるタイミング: 値がセットされる直前
didSet { print("did set from \(oldValue)") } // 呼ばれるタイミング: 値がセットされた直後
}
}
// Kotlin
class Rect(
var width: Double,
var height: Double
) {
val area: Double
get() = width * height // 計算プロパティ
val cache: MutableMap<String, Any> by lazy { mutableMapOf() }
var name: String = "rect"
set(value) {
println("will set to $value")
field = value // バックフィールド必須
println("did set from $field")
}
}
3.5 可視性とモジュール(internal/public
vs internal/public/module
概念)
Swift:
- デフォルトは
internal
(同一モジュール内で可視)。public
は外部から見えるが継承・オーバーライドは不可、外部で継承を許すならopen
。他にfileprivate
/private
。
Kotlin:
- デフォルトは
public
。internal
は同一モジュール内。private
/protected
あり。クラスの継承はopen
を付けないと不可能(デフォルトfinal
)。
例:
// Swift
public class A {} // 他モジュールから見えるが継承は不可
open class B {} // 他モジュールからの継承OK
internal struct C {} // 同一モジュールのみ
private func f() {}
// Kotlin
open class A // open で継承許可(デフォは final)
internal class B // 同一モジュールのみ
private fun f() {}
※ module:Kotlin は Gradle のモジュール単位。Swift も SwiftPM のパッケージ/ターゲット単位で概念は近い。
3.6 ジェネリクス(型制約・where
句 ↔ 上界/下界・reified
)
Swift:
<T: Protocol>
、複数はwhere
。
Kotlin:
<T: Interface>
、複数はwhere T: A, T: B
。reified
:Kotlin の inline 関数だけで使える“実行時に型を取り出せる”仕組み。Swift には直接的な対応なし(is
/as?
はあるがジェネリック実行時型情報は基本消える)。
例:
// Swift:最大値(Comparable 制約)
func maxOf<T: Comparable>(_ a: T, _ b: T) -> T { a > b ? a : b }
// Swift:複数制約
func encodeJSON<T>(_ v: T) -> Data where T: Encodable { try! JSONEncoder().encode(v) }
// Kotlin:Comparable 制約
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b
// Kotlin:複数制約
fun <T> encodeJson(v: T): String where T : Any = /* 省略 */
// Kotlin:reified(型でフィルタ)
inline fun <reified T> List<*>.only(): List<T> =
this.filterIsInstance<T>()
val xs: List<Any> = listOf(1, "a", 2)
val ints = xs.only<Int>() // [1, 2]
3.7 演算子とオーバーロード、等価性(Equatable/Hashable
↔ equals/hashCode
)
Swift:
Equatable
/Hashable
に準拠すると==
/集合キーが使える。Comparable
で<
等。独自演算子はstatic func
を定義。
Kotlin:
==
は構造的等価(equals
)、===
は参照等価。compareTo
実装で<
等が使える。operator
修飾子で演算子オーバーロード。
例:
// Swift
struct Point: Equatable, Hashable, Comparable {
let x: Int, y: Int
static func < (lhs: Point, rhs: Point) -> Bool {
(lhs.x, lhs.y) < (rhs.x, rhs.y)
}
}
let a = Point(x: 1, y: 2)
let b = Point(x: 1, y: 2)
print(a == b) // true
// Kotlin
data class Point(val x: Int, val y: Int): Comparable<Point> {
override fun compareTo(other: Point): Int =
compareValuesBy(this, other, Point::x, Point::y)
}
val a = Point(1, 2)
val b = Point(1, 2)
println(a == b) // true(構造)
println(a === b) // false(参照)
※ Kotlin で data class
は equals
/hashCode
/toString
/copy
を自動生成。Swift の struct
も合成実装が付く(単純な Stored Property のみの場合)。
型の考え方
4.1 値型中心 vs 参照型中心(コピーセマンティクスと不変性)
Swift:
- 値型(struct/enum)中心。代入でコピー(実装は多くが COW:必要になったときだけコピー)。不変(let)推奨でバグ減。
Kotlin:
- 参照型中心(
data class
も参照型)。**イミュータブルに“運用”**する(val
+copy
)。
例:
// Swift
struct Counter { var value = 0 }
var c1 = Counter()
var c2 = c1 // コピー
c2.value = 10
print(c1.value) // 0(影響しない)
// Kotlin:copy で不変運用
data class Counter(val value: Int = 0)
val c1 = Counter()
val c2 = c1.copy(value = 10)
println(c1.value) // 0(影響しない)
※ 設計目安:ドメインの“データ”は不変、振る舞いは別へ(UseCase/Service)。Swift は struct、Kotlin は data class + copy。
4.2 代数的データ型で表現力を上げる(switch
網羅性 vs when
網羅性)
大目的:状態/結果/イベントを型で漏れなく表す。
コツ:else
を書かないで網羅させる習慣をつけると、追加時にコンパイルが気付いてくれる。
例:
// Swift:関連値付き enum
enum AuthState {
case signedOut
case signingIn(progress: Double)
case signedIn(userID: String)
}
func render(_ s: AuthState) {
switch s {
case .signedOut:
print("Sign in")
case .signingIn(let p):
print("Loading \(p)")
case .signedIn(let id):
print("Welcome \(id)")
}
// すべて列挙しないとコンパイルエラー(網羅性)
}
// Kotlin:sealed interface + data class
sealed interface AuthState {
data object SignedOut: AuthState
data class SigningIn(val progress: Double): AuthState
data class SignedIn(val userId: String): AuthState
}
fun render(s: AuthState) = when (s) {
AuthState.SignedOut -> println("Sign in")
is AuthState.SigningIn -> println("Loading ${s.progress}")
is AuthState.SignedIn -> println("Welcome ${s.userId}")
// else 不要(sealed の網羅性)
}
4.3 Null安全の実務運用(アンラップ戦略・早期 return・!!
禁止令)
基本ルール:
- 入口で早期 return(Guard/Early return)
- 変換で非 null に寄せる(デフォルト値・マッピング)
!!
(Kotlin)や!
強制(Swiftas!
/try!
/!
)は極力使わない- チーム規約例:
!!
禁止、レビューで即指摘。どうしても必要なら直前で理由をコメント。
例:
// Swift:guard で早期 return
func load(userID: String?) -> String {
guard let userID else { return "guest" }
return "user: \(userID)"
}
// Swift: チェーンとデフォルト
let length = user?.name?.count ?? 0
// Kotlin:Elvis で変換
fun load(userId: String?): String =
"user: " + (userId ?: return "guest")
// Kotlin: チェーンとデフォルト
val length = user?.name?.length ?: 0
4.4 型変換・キャスト(as?/as!
vs as?/as
)
Swift:
as?
… 失敗すると nil(安全)as!
… 失敗するとクラッシュ(危険)
Kotlin:
as?
… 失敗すると null(安全)as
… 失敗すると 例外(ClassCastException
)
例:
// Swift
let any: Any = "Hello"
if let s = any as? String {
print(s.count)
}
// let n = any as! Int // 危険:クラッシュ
// Kotlin
val any: Any = "Hello"
val s = any as? String
println(s?.length)
// val n = any as Int // 危険:ClassCastException
※ 実務:安全キャスト(as?
)→ null ハンドリングが基本。失敗は型設計の見直しサイン。