接口概述
接口类型是对其他类型行为的抽象和概括,类似于C++中的多态,在运行时绑定。通过这种抽象的形式,可以让我们的方法更加灵活和更具有适应能力。
下面这粗体段话,我把我能理解的说一下:
- 隐式实现,是指只要满足接口的定义,就能当做接口使用,不需要显示的说我要继承并实现这些接口。
- 接口类型尽量小一点,通过组合接口行为实现复杂的对象行为集;
这个后面,我会举一个例子
很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地拥有一些必需的方法就足够了。这种设计可以让你创建一个新的接口类型满足已经存在的具体类型却不会去改变这些类型的定义;当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。
接口类型
接口类型描述了一系列方法集合,io.Writer接口类型是用得最广泛的接口之一。我们可以看看这些接口类型:
type Writer interface { Write(p []byte) (n int, err error)}type Reader interface { Read(p []byte) (n int, err error)}type Closer interface { Close() error}
我们可以看到一个行为一个接口,Go语言标准库中有很多这样的写法,主要考虑点:各个细粒度接口类型可以根据开发者的需要灵活的组合,形成强大的数据对象。例如:
type ReadWriter interface { Writer Reader}type ReadWriteCloser interface{ Writer Reader Closer}type ReadWriter interface{ Write(p []byte) (n int, err error) Reader}
通过这个,我来说一个很实用的小trick。问题描述:判断一个数据对象是否具有某个指定的方法?
func writeString(w io.Writer, s string) (n int, err error) { type Writer interface{ WriteString(string) (int, error) } if sw, ok:= w.(Writer); ok { return sw.WriteString(s) } return w.Write(s)}
正因为Go语言的接口是隐式实现的,所以这带来很非常大的方便。
实现接口的条件
一个类型如果拥有一个接口所需要的所有方法,则这个类型就实现了这个接口。接口指定的规则:
var w io.Writerw = os.Stdoutw = new(bytes.Buffer)var rwc ReadWriteCloserw = rwc // ... 接口对象可以赋值给接口对象,编译时不会报错,如果接口对象内存值为nil,则会在运行时panic。
这里说一个:临时变量不能寻址: IntSet{}.String()
,编译报错。var set IntSet; set.String()
, 编译成功。
后者编译通过,是编译器会按照类型声明进行数据对象的隐式改变。但是下面这种编译器会报错:
var s IntSetvar _ fmt.Stringer() = &s // compile okvar _ fmt.Stringer() = s // compile error
接口类型封装和隐藏数据对象的方法和值。即使具体类型有其他的方法也只能通过接口类型暴露出来的方法能够被调用到:
os.Stdout.Write([]byte("hello,world")var w io.Writerw = os.Stdout // os.Stdout有Writer、Reader和Close接口w.Close() // complie error
如果一个接口类型的方法集是空的,那么这个接口的数据对象能够接收任何数据类型,且通过断言来校验你想要的类型数据对象。
接口很多初学者要犯的错
我在Github上看到过很多项目有过这样类似的校验:
func main() { var a *int var b interface{} b = a if b == nil { fmt.Println("b is nil") } else { fmt.Println("b is non-nil") }}
请问上面的输出是什么?, Github上很多开发者会回答:b is nil
接口数据对象接口值是由类型和类型值两部分构成的,上面这个例子,b是有类型的int类型的指针。只是接口值是nil。如果你要校验原始的数据对象是否为nil,有两种做法:
- 第一种,通过反射reflect校验
- 第二种,通过类型值校验
value := reflect.ValueOf(b)if value.IsNil() { fmt.Println("b is actually nil value")}
第二种:
var NilPtr = (*int)(nil)if b == NilPtr { fmt.Println("b is actually nil value")}