A Tour of Go vol.7(Interfaces/Type assertions)
Interfaces
・メソッドの型だけを定義した型
・オブジェクトの振る舞いを定義する
下記の例では Abserをインターフェースとする変数aに対し、Abserで定義しているメソッドAbs()を実装していない型の変数を代入しようとしてエラーになる。
// インターフェースAbserはメソッドAbsを振る舞いとして定義 type Abser interface { Abs() float64 } func main() { var a Abser f := MyFloat(-math.Sqrt2) v := Vertex{3, 4} a = f // a MyFloat implements Abser -> 1 a = &v // a *Vertex implements Abser -> 2 // In the following line, v is a Vertex (not *Vertex) // and does NOT implement Abser. a = v fmt.Println(a.Abs()) } type MyFloat float64 // 1: MyFloatはメソッドabs()を持つ func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } type Vertex struct { X, Y float64 } // 2: *Vertex(ポインタ型)はメソッドabs()を持つ func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
インターフェースを実装する際はimplementsの表記は必要ない。
インターフェースのわかりやすい実装例
dev.classmethod.jp
nil
一旦離脱するがGoではnilは型を持つ
func main() { var i32 *int32 fmt.Println(i32 == nil) // true var arr []int fmt.Println(arr == nil) // true fmt.Println(compare(i32, arr)) // false }
基本的にnilは "==nil" がtrueになるが、インターフェースの時だけ型もnilでないと==nilはfalseとなる。
func main() { var arr []int fmt.Println(arr == nil) // true fmt.Println(reflect.ValueOf(arr).IsNil()) // true fmt.Println(reflect.TypeOf(arr)) // []int var arr2 interface{} = arr fmt.Println(arr2 == nil) // false fmt.Println(reflect.ValueOf(arr2).IsNil()) // true fmt.Println(reflect.TypeOf(arr2)) // []int var arr3 interface{} fmt.Println(arr3 == nil) // true fmt.Println(reflect.TypeOf(arr3)) // <nil> }
arr2はinterfaceとして扱っているため、型がnilでないので== nilはfalseとなる
isNilはあくまでBalueOfの下から値がnilだということを言いたいのか。
== nilは値に限らずnilをチェックしているのだろうが深堀はしない。
Type assertions
インターフェースはどんな型でも受け入れるので引数の型をインターフェース型にすればどんな型の値も受け取る関数をつくることができる。
しかし、この方法で受け渡された値は元の型がなにだったのかということを知ることができない。
この時に型アサーションを使えば動的に元の型がなんであるのかをチェックすることができる。
value, ok := <変数>.(<型>)
valueには型アサーション成功時に実際の値が格納され、2番目の変数okにはアサーションの成功否が格納される。
func main() { printIf(12) printIf("hello") printIf([]string{"cat", "dog"}) printIf([2]string{"hello", "world"}) } func printIf(src interface{}) { if value, ok := src.(int); ok { fmt.Printf("parameter is integer. [value: %d]\n", value) return } if value, ok := src.(string); ok { value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) return } if value, ok := src.([]string); ok { value = append(value, "unknown") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) return } fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) }
Type switches
型アサーションと分岐を組み合わせた処理を手軽に記述することができる。
func printIf(src interface{}) { switch value := src.(type) { case int: fmt.Printf("parameter is integer. [value: %d]\n", value) case string: value = strings.ToUpper(value) // 対象がstring型なのでstringを引数に取る関数が実行できる fmt.Printf("parameter is string. [value: %s]\n", value) case []string: value = append(value, "<不明>") // 対象がsliceなのでAppendができる fmt.Printf("parameter is slice string. [value: %s]\n", value) default: fmt.Printf("parameter is unknown type. [valueType: %T]\n", src) } }