業務でSwiftに携わり始めた頃、クラスやメソッド名の隣に書かれている<>
の意味が分からずに困っていました。しかもなんて調べたら<>
←この正体に出会えるのかも分からない。
随分後になってこれがジェネリクスという記法だということを知りました。
そんなジェネリクスについてまとめたいと思います。
ジェネリクスとは
汎用的な実装をするための技術です。
通常関数の引数はInt
やString
など、型を決めて宣言します。もちろん宣言した型以外は変数に入れることはできません。しかしジェネリクスを使うと、同じ関数の引数に違う型を渡すことができるのです。
Swiftにはジェネリック関数
とジェネリック型
があります。
ジェネリック関数
型引数(<>
で囲まれたやつ)をもつ関数のことです。
func get<T: Equatable>(_ x: T) -> T {
// 返り値は引数と同じ型になる
return x
}
get(1) // 1: Int
sum("a") // a: String
// 複数の引数を違う方で受け取りたい場合、型引数を2つ定義する
func get<T, U>(_ x: T, y: U) { }
get(1, "String")
<T: Equatable>
はEquatableプロトコルに準拠した型を意味します。これを型制約と言います。
sum(1, 2)
のように、ジェネリック関数またはジェネリック型に具体的な型引数を与えて型を確定することを特殊化と言います。
ジェネリック型
型引数をもつクラスや構造体・列挙型のことです。
// クラス
class SampleClass<T> {}
// 構造体
struct SampleStruct<T> {}
// 列挙型
enum SampleEnum<T> {}
型の安全性は保たれるのか?
ジェネリクスは汎用性と型の安全性を兼ね備えています。対象にジェネリクスと同様に汎用的な実装を可能にするAny
は型の安全性を備えていません。Any
は暗黙的に型推論される為、具体的な型として扱いたい時はダウンキャストが必要になります。
func get<T: Equatable>(_ x: T) -> T {
return x
}
get(1) // 1: Int
// ------- 先ほどのこれをAnyを使って実現させたい場合---------
func get(_ x: Any) -> Any {
if let intX = x as? Int { return intX }
}
let anyGet = get(1) // Any型
if let intGet = anyGet as? Int { // get(1)のInt型が取れる }
Any
は具体的な引数を渡してもAny
型のままなので、こんな感じで都度ダウンキャストする必要があります。
ジェネリクスとAny
を使い分けて活用すればかなり汎用的な実装が可能になりそうですね。
*2020/7/1 コメント頂きもう少し分かりやすく修正しました。