golangのinterfaceは使う機会も多いけど雰囲気で使っていた部分があります。
基本的なところからおさらい。
インターフェース
インターフェースはメソッド定義リストで、それを実装した型を受け入れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import "fmt" type I interface { SomeMethod() string } type S struct { V float64 } func (s S) SomeMethod() string { return fmt.Sprint(s.V) } func main() { var i I //i = 12 //error: cannot use 12 (type int) as type I in assignment: // int does not implement I (missing SomeMethod method) i = S{3.14} fmt.Println(i) //-> {3.14} } |
intを入れようとすると未実装エラーになります。
レシーバとしてSomeMethod()
を実装した構造体Sは入れることができました。
これは関数実装などで特定のメソッドを実装した型を受け入れて処理を行ったりするのに使われます。
1 2 3 4 5 6 7 8 |
func Method(i I) string { return "this is " + i.SomeMethod() } func main() { // S は I を満たすので渡すことができる fmt.Println(Method(S{3.14})) //-> this is 3.14 } |
これによって複数の型(構造体)に共通するメソッドを作ることが可能になります。
こういったメソッドを作るために必要なメソッド定義がインターフェースです。
インターフェースは処理に必要なものだけ定義することを忘れないようにします。
空のインターフェース interface{}
引数などで一番よく見かけるものだと思います。
1 2 3 |
func Println(a ...interface{}) (n int, err error) { return Fprintln(os.Stdout, a...) } |
私は最初に調べた時interface
とinterface{}
は別物だと見かけて変な思い込みがありましたがこれは同じものです。実装するべきメソッドが空なのでどんな型でも受け入れます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" type S struct { V float64 } func main() { var i1, i2, i3 interface{} i1 = 12 i2 = "interface" i3 = S{3.14} fmt.Println(i1, i2, i3) //-> 12 interface {3.14} } |
type assertion と type switch
インターフェースは定義したメソッドを使用可能ですが、元の型(構造体)のメソッドやメンバを使うことができません。
型アサーションを使うことで元の構造体へ戻すことができます。
1 2 3 4 5 6 |
func TypeAssertion(i I) { v, ok := i.(S) if ok { fmt.Println("type assertion", v.V)//-> 3.14 } } |
複数の型の処理にはtype switchが便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func TypeSwitch(i interface{}) { switch v := i.(type) { case int: fmt.Println("int:", v) case float64: fmt.Println("float64:", v) case string: fmt.Println("string:", v) default: fmt.Println("other:", v) } } func main() { TypeSwitch(1) //-> int: 1 TypeSwitch(3.14) //-> float64: 3.14 TypeSwitch("if") //-> string: if TypeSwitch(func() {}) //-> other: 0x490850 } |
インターフェースのポインタ
参照渡しで何でも受け入れる関数なんかを作ろうと考えてみる。
何でも入る空のinterface{}
のポインタを引数にしてみるとこうなる。
1 2 3 4 5 6 7 8 9 |
func main(){ i := 12 interfacep(&i) //cannot use &i (type *int) as type *interface {} in argument to interfacep: //*interface {} is pointer to interface, not interface } func interfacep(ip *interface{}) { fmt.Println(*ip) } |
インターフェースのポインタじゃなくてintのポインタだと怒られる。
なんらかの定義されたインターフェースなら渡せるのか。
1 2 3 4 5 6 |
type II interface { } ... var i II = 12 interfacep(&i) //cannot use &i (type *II) as type *interface {}... |
実態は同じものだけど、これも通らない。
インターフェースのポインタはインターフェースのポインタしか受け付けない。
1 2 |
var i interface{} = 12 interfacep(&i) //-> 12 |
使い道もなくはなさそうだけど機会はまずないとおもう。
そもそもポインタもインターフェースとして扱えるのでそのまま渡すことができる。
1 2 3 4 5 6 7 |
func main(){ i := 12 interfacep(&i) } func interfacep(i interface{}) { fmt.Println(*(i.(*int))) } |