Go接口

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 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。

还有一些不常用的方式(当后缀 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)

 接口的继承

golang 使用组合实现继承

========END========

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

三十分钟入门基础Go(Java小子版)

本篇文章适用于学习过其他面向对象语言(Java、Php),但没有学过Go语言的初学者。文章主要从Go与Java功能上的对比来阐述Go语言的基础语法、面向对象编程、并发与错误四个方面。

一文吃透 Go 语言解密之接口 interface

转载:大家好,我是煎鱼。自古流传着一个传言...在Go语言面试的时候必有人会问接口(interface)的实现原理。这又是为什么?为何对接口如此执着?实际上,Go语言的接口设计在整体扮演着非常重要的角色,没有他,很多程序估计都跑的不愉快了。在Go语言的语义上,只要某个类型实现了所定义的一组方法集,则就认为其就是同一种类型,是一个东西。大家常常称其

Javascript 是如何体现继承的 ?

js继承的概念js里常用的如下两种继承方式:原型链继承(对象间的继承)类式继承(构造函数间的继承) 由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念。所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现在面向对象的语言中,我们使用类来创建一个自定义对象