一个接口类型定义了许多行为,每个函数代表一种行为,一个实现了这些方法的具体类型就是这个接口类型的实例。
实现接口
如果一个类型实现了一个接口需要的所有方法,那么该类型就实现了这个接口。Go的程序员经常会简要的把一个具体的类型描述成一个特定的接口类型。如:
var w io.Writer
w = os.Stdout // OK: *os.File has Write method
w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method
w = time.Second // compile error: time.Duration lacks Write method
对于每一个命名过的具体类型T;它一些方法的接收者是类型T本身然而另一些则是一个*T的指针。在T类型的参数上调用一个*T的方法是合法的,只要这个参数是一个可寻址变量,编译器会帮我们做隐式转换。但这仅仅是一个语法糖:T类型的值不拥有所有*T指针的方法,那这样它就可能只实现更少的接口。注意:我们不能在不能寻址的值上调用调用指针定义的方法
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
然而,由于只有*IntSet类型有String方法,所以也只有*IntSet类型实现了fmt.Stringer接口:
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
接口的值
概念上讲一个接口的值由两个部分组成,一个具体的类型和那个类型的值。当一个接口被声明时,它被初始化为零值,即它的类型和值的部分都是 nil,如:var w io.Writer
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。因为接口值是可比较的,所以它们可以用在map的键或者作为switch语句的操作数。如果动态类型的值不可比较,那么会比较失败并且 panic
包含nil指针的接口
一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。
func test(debug bool) io.Writer{
var out *bytes.Buffer
if debug {
out = new(bytes.Buffer)
out.Write([]byte("Done"))
}
return out
}
上述程序会引发 panic,panic: nil pointer dereference
原因是out的类型值为非空,只是包含了一个nil指针,所以判断里的语句违反了(*bytes.Buffer).Write
方法的接收者非空的隐含先觉条件,正确的做法是将 out 声明改为 *io.Writer
类型断言
类型断言是一个使用在接口上的操作。一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。
var w io.Writer
w = os.Stdout
f := w.(*os.File) // success: f == os.Stdout
c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
断言类型也可以是一个接口类型
var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // success: *os.File has both Read and Write
w = new(ByteCounter)
rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
当我们对一个接口值的动态类型不是很确定时,我们会用类型断言去检验它是否是某个特定类型
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
if f, ok := w.(*os.File); ok {
// ...use f...
}