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========