はじめに
Goの基本文法シリーズ、今回で最後となりました。
ラストはGo学習者が混乱すると評判の「Interfaces : インターフェース」についてです。
目次
Interfaces : インターフェース
インターフェースとは、メソッドの塊のことです。
メソッドの型だけを定義した型をinterface 型
と言います。
インターフェースの定義方法は以下です。
type 型名 interface {
メソッド名1(引数の型) (返り値の型)
メソッド名2(引数の型) (返り値の型)
}
では、このインターフェースがどう使われるのか?
もう少し具体的に説明していきます。
package main
import "fmt"
// Country インターフェースを生成
type Country interface {
// Language メソッドを持たせる
Language() string
}
// 構造体 Japan を生成(型:Japan)
type Japan struct {}
// 構造体 Japan が Country インターフェースを実装
func (c Japan) Language() string {
return "Japanese"
}
// 構造体 USA を生成
type USA struct {}
// 構造体 USA が Country インターフェースを実装
func (c USA) Language() string {
return "English"
}
func main() {
// Country インターフェース型のスライスを生成
countries := []Country{Japan{}, USA{}}
// 生成したスライスに格納された値を反復処理で出力
for _, country := range countries {
fmt.Println(country.Language())
}
}
↑で混乱するとすれば、func (c Japan) Language() string {〜
の部分ですね。
構造体(Japan
)がインターフェース(Country
)を実装したことを意味しています。
幾つか使用例をご紹介します。
まずは、メソッドへのインターフェースの実装です。定義方法は以下です。
※ メソッド名 : インターフェース内のメソッド名と合わせる
※ 引数の型 : インターフェース内のメソッドの型と合わせる
func (引数 レシーバー名) メソッド名() 引数の型 {
メソッドの中身
}
// Country インターフェース
type Country interface {
Language() string
}
// 構造体 Japan
type Japan struct {}
// 構造体 Japan が Country インターフェースを実装
func (c Japan) Language() string {
return "Japanese" // => Japanese
}
また、インターフェースを代入した変数へ構造体を代入した変数を代入(構造体(Japan
)がインターフェース(Country
)を実装)することで、インターフェースから構造体が持つメソッドを呼び出すパターンもあります。
// Country インターフェース
type Country interface {
Language() string
}
var country Country // Country インターフェースを変数へ代入
var language Japan // 構造体 Japan を変数へ代入
country = language // Country インターフェースへ構造体 Japan を代入
// Country インターフェースから Language メソッドを呼び出す
fmt.Println(country.Language())
上記のように、最低限のメソッドを各インターフェースで定義することで、インターフェース単位で呼び出すことができるメソッドを明確化・区別することができます。
空インターフェース
全ての型と互換性を持つinterface{}型(空インターフェース)
というものもあります。
文字通りゼロ個のメソッドを指定された interface 型のことです。
空インターフェースの定義方法は以下です。
interface{}
どんな型でも代入可能です。
var i interface{}
i = 100 // int
i = "Interface" // string
i = []string{"Go", "Ruby", "PHP"} // slice
i = func say(_ string) string { return "Hello" } // function
型アサーション
interface{}
で引き渡された値というのは元の型の情報が欠落しています。
型アサーションは、インターフェースの値に具体的な型を指定・利用することができます。
型アサーションの定義方法は以下です。
value := 変数.(型)
以下、型アサーションの使用例です。
「具体的な型を指定」の意味がわかるかと思います。
ちなみに、データの型判定を行うことをアサーションと言います。
package main
import "fmt"
func main() {
// 空インターフェースを生成
var i interface{} = "hello"
// 型を持たない空インターフェースの型として string 型を指定、変数へ代入
s := i.(string)
fmt.Println(s) // => hello
// 変数 i が string 型かどうかアサーション
// string 型のため true
s, check := i.(string)
fmt.Println(s, check) // => hello true
// 変数 i が float64 型かどうかアサーション
// float64 型でないため false
f, check := i.(float64)
fmt.Println(f, check) // => 0 false
// この時点でインターフェース i は string 型指定されている
// float64 型として指定・利用しようとするとエラーになる
f = i.(float64)
fmt.Println(f) // => panic: interface conversion: interface {} is string, not float64
}
型switch
データの型判定は、型switch でも可能です。
型switchの定義方法は以下です。
以下の様に switch の後ろに型アサーションv := i.(type)
を書き、case
に型を指定します。case
の型とインターフェースの値の型を比較します。
switch v := i.(type) {
case 型1: ... // v は型1 の値になる
case 型2: ... // v は型2 の値になる
...
default: ...
}
以下、型アサーションの使用例です。
インターフェースの値i
の型とcase
で指定された型を比較しています。
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21) // => Twice 21 is 42
do("hello") // => "hello" is 5 bytes long
do(true) // => I don't know about type bool!
}
上記の通り、型switch により柔軟に型アサーションを行うことができます。