前回の記事はこちら:
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) // 63.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 ?: 04.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 ハンドリングが基本。失敗は型設計の見直しサイン。
