Go接口的定义
Go 语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。但是 Go 语言里有非常灵活的 接口(Interfaces are named collections of method signatures) 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为。接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
通过如下格式定义接口:
type Namer interface { Method1(param_list) return_type Method2(param_list) return_type ... }
上面的 Namer 是一个 接口类型。
(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r 后缀组成,例如 Printer、Reader、Writer、Logger、Converter 等等。
还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NET 或 Java 中那样)。
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。
接口的实现
Go 语言中的接口很特别,而且提供了难以置信的一系列灵活性和抽象性。
下面定义一个接口:
type Notifier interface { Notify() error }
我们定义了一个叫做 Notifier 的接口并包含一个 Notify 方法。当一个接口只包含一个方法时,按照 Go 语言的约定命名该接口时添加 -er 后缀。这个约定很有用,特别是接口和方法具有相同名字和意义的时候。
Go 语言不需要我们显式的实现类型的接口。如果一个接口里的所有方法都被我们的类型实现了,那么我们就说该类型实现了该接口。
package main import ( "log" ) type Notifier interface { Notify() error } type User struct { Name string Email string } // User 结构体类型实现了接口 Notifier func (u *User) Notify() error { log.Printf("User: Sending User Email To %s<%s>\n", u.Name, u.Email) return nil } func SendNotification(notify Notifier) error { return notify.Notify() } func main() { user := &User{ Name: "AriesDevil", Email: "ariesdevil@xxoo.com", } user.Notify() SendNotification(user) }
空接口(empty interface) - interface{}
go 允许不带任何方法的 interface ,这种类型的 interface 叫 empty interface。
空的 interface 没有方法,所以可以认为所有的类型都实现了 interface{}。
如果定义一个函数参数是 interface{} 类型,这个函数应该可以接受任何类型作为它的参数。
func doSomething(v interface{}){ }
既然空的 interface 可以接受任何类型的参数,那么一个 interface{}类型的 slice 是不是就可以接受任何类型的 slice ?
func printAll(vals []interface{}) { //1 for _, val := range vals { fmt.Println(val) } } func main(){ names := []string{"stanley", "david", "oscar"} printAll(names) }
上面的代码是按照我们的假设修改的,执行之后竟然会报 cannot use names (type []string) as type []interface {} in argument to printAll 错误,why?
这个错误说明 go 没有帮助我们自动把 slice 转换成 interface{} 类型的 slice,所以出错了。go 不会对 类型是interface{} 的 slice 进行转换 。
原因是它们俩在内存中的布局是不一样的(参考 官方说明)。
必须使用 for-range 语句来一个一个显式地复制:
var dataSlice []myType = FuncReturnSlice() var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) for ix, d := range dataSlice { interfaceSlice[i] = d }
接口的值
在golang中,接口值是由两部分组成的,一部分是接口的类型,另一部分是该类型对应的值,我们称其为动态类型和动态值。看如下代码打印接口的动态类型和动态值。
package main import ( "fmt" "reflect" ) func main() { var val interface{} = int64(58) fmt.Println(reflect.TypeOf(val)) // int64 fmt.Println(reflect.ValueOf(val)) // 58 val = 50 fmt.Println(reflect.TypeOf(val)) // int fmt.Println(reflect.ValueOf(val)) // 50 val = string("hello world") fmt.Println(reflect.TypeOf(val)) // string fmt.Println(reflect.ValueOf(val)) // hello world val = Point{1, 2} fmt.Println(reflect.TypeOf(val)) // main.Point fmt.Println(reflect.ValueOf(val)) // {1 2} val = &Point{2, 3} // 此时 val 的类型是 *Point fmt.Println(reflect.TypeOf(val)) // *main.Point fmt.Println(reflect.ValueOf(val)) // &{2 3} val = nil fmt.Println(val) // <nil> fmt.Println(reflect.TypeOf(val)) // <nil> fmt.Println(reflect.ValueOf(val)) // <invalid reflect.Value> } type Point struct{ X, Y float64 }
接口的nil 值
定义一个变量w ,声明了其类型为io.Writer,
func main() { var w io.Writer fmt.Printf("w.type = %T ,nil = %t \n", w, w == nil) //w.type = <nil> ,nil = true }
w是nil 是因为
按照Go语言规范,任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针、函数、interface、slice、channel和map的零值都是nil。
interface的实际实现结构可以理解为下图:

其中type就是它的类型(动态类型),value部分是它的值(动态值)。
一个interface类型的变量 w 为nil,就代表着其动态类型和动态值都为 nil 。对于一个接口的零值就是它的类型和值的部分都是nil。
但是,考虑这种情况:

图中代表着类型不为空,但是interface的动态值是 nil ,那么这种情况下,如果去判断 w 是否为 nil 时,会得到一个false
fmt.Println(w == nil) //输出 false
要特别注意这钟情况,因为我们可能会犯这样的错误:
if w != nil { w.Write([]byte("done!\n")) // 当w的动态值为nil时,会发生panic }
比如这面这段代码,
package main import ( "bytes" "fmt" "io" ) /** io.Writer 是一个接口 接口有值 动态类型 和 动态值 */ func test(w io.Writer) { fmt.Printf("2.type = %T ,nil = %t\n", w, w == nil) if w != nil { w.Write([]byte("ok")) } } func main() { /** buf 是一个结构体类型,可以赋值给它实现的接口 */ var buf *bytes.Buffer fmt.Printf("1.type = %T ,nil = %t \n", buf, buf == nil) test(buf) }
报错输出如下,
1.type = *bytes.Buffer ,nil = true panic: runtime error: invalid memory address or nil pointer dereference 2.type = *bytes.Buffer ,nil = false [signal SIGSEGV: segmentation violation code=0x1 addr=0x60 pc=0x105f4e6] goroutine 1 [running]: bytes.(*Buffer).Write(0x0, 0xc000074008, 0x2, 0x8, 0x2, 0x8, 0x0) /usr/local/go/src/bytes/buffer.go:171 +0x26 main.test(0x10d9b40, 0x0) /Users/xinxingegeya/workspace-go/src/gitee.com/xxggy/go-learning/test/temp/temp.go:16 +0x10d main.main() /Users/xinxingegeya/workspace-go/src/gitee.com/xxggy/go-learning/test/temp/temp.go:23 +0xab Process finished with exit code 2
我们来稍微分析一下,在main()中,我们首先声明了一个buf变量,类型是*bytes.Buffer指针类型,值是指针的零值nil。在调用函数test()的时候,参数w会被赋值为动态类型为*bytes.Buffer,动态值为nil,也就是w是一个包含了空指针值的非空接口。那么在w != nil判断时,这个等式便是成立的,不过这里也从侧面反映出一个现象,就是这种传参都是值拷贝。
接口嵌套接口
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
比如接口 File 包含了 ReadWrite 和 Lock 的所有方法,它还额外有一个 Close() 方法。
type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool } type Lock interface { Lock() Unlock() } type File interface { ReadWrite Lock Close() }
类型断言:如何检测和转换接口变量的类型
var v interface{}
一个接口类型的变量 v 中可以包含任何类型的值,必须有一种方式来检测它的 动态 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 类型断言 来测试在某个时刻 v 是否包含类型 T 的值:
value, ok := element.(T)
element必须是接口类型的变量,T是普通类型。如果断言失败,ok为false,否则ok为true并且value为变量的值。来看个例子:
package main import ( "fmt" ) type Html []interface{} func main() { html := make(Html, 5) html[0] = "div" html[1] = "span" html[2] = []byte("script") html[3] = "style" html[4] = "head" for index, element := range html { if value, ok := element.(string); ok { fmt.Printf("html[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.([]byte); ok { fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value)) } } }
其实断言还支持另一种简化使用的方式:value := element.(T)。但这种方式不建议使用,因为一旦element.(T)断言失败,则会产生运行时错误。如:
package main import ( "fmt" ) func main() { var val interface{} = "good" fmt.Println(val.(string)) // fmt.Println(val.(int)) }
以上的代码中被注释的那一行会运行时错误。这是因为val实际存储的是string类型,因此断言失败。
还有一种转换方式是switch测试。既然称之为switch测试,也就是说这种转换方式只能出现在switch语句中。可以很轻松的将刚才用断言的例子换成由switch测试来实现:
package main import ( "fmt" ) type Html []interface{} func main() { html := make(Html, 5) html[0] = "div" html[1] = "span" html[2] = []byte("script") html[3] = "style" html[4] = "head" for index, element := range html { switch value := element.(type) { case string: fmt.Printf("html[%d] is a string and its value is %s\n", index, value) case []byte: fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value)) case int: fmt.Printf("invalid type\n") default: fmt.Printf("unknown type\n") } } }
CASE 一
em必须为interface类型才可以进行类型断言
比如下面这段代码,
s := "hello world" if v, ok := s.(string); ok { fmt.Println(v) }
运行报错, invalid type assertion: s.(string) (non-interface type string on left)
在这里只要是在声明时或函数传进来的参数不是interface类型那么做类型断言都是回报 non-interface的错误的 所以我们只能通过将s作为一个interface{}的方法来进行类型断言,如下代码所示:
x := "hello world" if v, ok := interface{}(x).(string); ok { // interface{}(x):把 x 的类型转换成 interface{} fmt.Println(v) }
将s显示的转换为interface{}接口类型则可以进行类型断言了
CASE 二
当函数作为参数并且被调用函数将参数类型指定为interface{}的时候是没有办法直接调用该方法的,如下面代码,
package main import ( "fmt" ) func ServeHTTP(s string) { fmt.Println(s) } type Handler func(string) func panduan(in interface{}) { Handler(in)("wujunbin") } func main() { panduan(Handler(ServeHTTP)) }
运行报错,
./assert.go:14: cannot convert in (type interface {}) to type Handler: need type assertion
改正如下,
package main import ( "fmt" ) func ServeHTTP(s string) { fmt.Println(s) } type Handler func(string) //使用 type 定义一个函数类型 func panduan(in interface{}) { v, ok := in.(Handler) if ok { v("hello world") } else { panic("assert fail") } } func main() { panduan(Handler(ServeHTTP)) }
只有让传进来的in参数先与Handler进行类型判断如果返回值是OK则代表类型相同才能进行对应的方法调用。
CASE 三
进行类型断言之后如果断言成功,就只能使用该类型的方法。 比如对一个结构体S进行与A接口断言,S实际上实现了A B两个接口,A interface 具有 a()方法,B interface 具有 b()方法,如果结构体S作为参数被传入一个函数中并且在该函数中是interface{}类型,那么进行与A的类型断言之后就只能调用a()而不能调用b(),因为编译器只知道你目前是A类型却不知道你目前也是B类型。看下面这个例子,
package main import ( "fmt" ) type InterfaceA interface { AA() } type InterfaceB interface { BB() } // Won 实现了接口 AA 和 BB type Won struct{} func (w Won) AA() { fmt.Println("AA") } func (w Won) BB() { fmt.Println("BB") } func info(in interface{}) { //必须经过 类型断言,才能调用相应的方法 v, ok := in.(InterfaceA) //必须进行相应的断言才能进行函数的调用,不然编译器不知道是什么具体类型 if ok { v.AA() } else { fmt.Println("assert fail") } } func main() { var x InterfaceA = Won{} info(x) var y Won = Won{} info(y) }
空接口和函数重载
函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 ...T 作为函数最后一个参数来实现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
函数 fmt.Printf 就是这样做的:
fmt.Printf(format string, a ...interface{}) (n int, errno error)
接口的继承
========END========