Go 言語で、ある型にインタフェースを実装するときは、Java などとは異なり、implements
キーワードを使ったインタフェースの明示は行いません。
Ruby や Python と同様に、同じシグネチャのメソッドを実装した時点で、そのインタフェースを備えているとみなされます。
インタフェースを定義する (type … interface)
Go 言語でインタフェースを定義するときは、構造体 (struct) の定義と同様に type
キーワードを使用します。
下記は Go 言語で定義されているインタフェースの例です。
上記の例からも想像できるように、Go 言語では、1 つのメソッドだけを持つインタフェースの名前は、メソッド名+er
とする規約となっています。
String
関数だけを持つ →Stringer
インタフェースRead
関数だけを持つ →Reader
インタフェースWrite
関数だけを持つ →Writer
インタフェースFormat
関数だけを持つ →Formatter
インタフェース
インタフェースを使用する
インタフェースを引数として受け取る関数を定義すると、そのインタフェースが定義するメソッドを実装しているオブジェクトだけを渡せるようになります。
func PrintSomething(s fmt.Stringer) {
// String() メソッドを呼び出せることが保証されている
fmt.Println(s.String())
}
Java などの言語では、あるクラスがあるインタフェースを実装していることを示すために implements
キーワードを使用しますが、Go 言語では明示的にインタフェース名を指定して実装することはありません。
ただ単純に、インタフェースによって示されているメソッドを実装するだけで、その型はそのインタフェースを備えている(実装している)とみなされます。
このような思想は Ruby や Python でも採用されており、通称 ダックタイピング と言われているものです(アヒルのように歩いて鳴けば、それはアヒルであるという考え方)。
例えば、下記の Book
構造体は、String()
メソッドを実装しているため、Go コンパイラは fmt.Stringer
インタフェースを備えているとみなします。
type Book struct {
Title string
Author string
}
func (this *Book) String() string {
return fmt.Sprintf("%s : %s", this.Title, this.Author)
}
よって、この Book
構造体のインスタンスは、先に示した PrintSomething
関数に渡せることになります。
b := &Book{Title: "Golang", Author: "Maku"}
PrintSomething(b) //=> "Golang : Maku"
また、Book
は fmt.Stringer
インタフェースを備えているので、fmt.Stringer
型の変数に代入することができます。
var s fmt.Stringer = &Book{Title: "Golang", Author: "Maku"}
println(s.String())
すべてのオブジェクトを示す空っぽインタフェース
ここで、次のようにメソッドを1つも持たない空っぽのインタフェース (empty interface) を考えてみます。
interface{}
Go 言語では、インタフェースが示すメソッドを実装していれば、そのインタフェースを備えていると判断されます。
つまり、上記のような空インタフェースは、すべての型が備えているインタフェースということになります(Java における Object
クラスに近い)。
関数のパラメータとして、inteface{}
型を受け取るように実装されている場合は、任意の型のオブジェクト を渡すことができるということを示しています。
例えば、fmt.Println
関数は、任意の型の引数を複数受け取るように実装されています。
func Println(a ...interface{}) (n int, err error)
また、空インタフェース型の変数を定義すると、その変数にはどのような型の値でも代入できるようになります。
var i interface{} // 空インタフェース型の変数を定義
空インタフェースのゼロ値は nil
で、型も nil
となります。
下記のサンプルコードは、空インタフェースに色々な型の値を代入できること、代入される値によって変数の型が変化することを示しています。
package main
import "fmt"
func printTypeAndValue(i interface{}) {
fmt.Printf("%v (type:%T)\n", i, i)
}
func main() {
var i interface{}
printTypeAndValue(i) // <nil> (type:<nil>)
i = 100
printTypeAndValue(i) // 100 (type:int)
i = "Hello"
printTypeAndValue(i) // Hello (type:string)
}
空インタフェースをパラメータとして受け取る関数の中で、型スイッチ (Type Switch) の仕組みを使用すれば、実際に渡された値の型によって処理を分岐することができます。