interface
接口就是定义了0个或多个方法的集合,它是两个事物对接的一个规范。实现了这个接口,就符合了这个规范。接口中只能声明方法,不能定义字段,也不能实现方法。这一点与java的interface还不太一样,在java的interface中,可以定义常量字段,而golang的interface中只能有方法。下边来看看定义一个接口的语法:
type Message interface{
Read() string
Write(str string)
}
上边的示例中,定义了一个名为Message的接口,接口中有两个方法,分别是Read和Write。Read方法返回一个string类型值,而Write方法接收一个string类型的参数。定义接口的语法非常简单。
接口实现与赋值
接口实现,就是某一个类型拥有这个接口全部声明的方法。
golang中任何类型,都可以去实现某一个或多个接口。以int类型为原型,定义一个新的类型myInt。让myInt类型来实现接口msg。示例代码如下:
package main
import (
"fmt"
)
// 定义接口,接口名称是msg,接口中有一个方法
type msg interface {
write(v string)
}
// 根据int定义一个新的类型myInt
type myInt int
// 给myInt类型添加方法
func (r myInt) write(v string) {
fmt.Println(v)
}
func main() {
var m msg = myInt(3)
m.write("hello wolrd")
}
从上边的示例代码可知,myInt类型实现了msg接口。当类型A实现了接口B之后,类型A的变量就可以被赋值给接口B类型的变量,如上边代码中,将myInt类型变量赋值给msg接口类型变量m。
在golang中,能够实现接口的不仅仅是由struct定义的结构体类型,可以是任意能够添加方法的类型,所谓任意能够添加方法的类型,就是本包中的类型,因为golang只允许给本包中的类型添加方法。
假如一个接口没有任何方法,那么所有的类型都实现了这个接口。没有方法的接口如下边示例所示:
package main
import (
"fmt"
)
type Any interface{}
func main() {
var fromInt Any = 10
var fromStr Any = "hello world"
var fromFloat Any = 3.14
fmt.Println(fromInt, fromStr, fromFloat)
}
输出结果是:
10 hello world 3.14
上边示例代码中,Any是一个没有任何方法的接口,在变量定义中,分别使用整型,字符串类型,浮点型来初始化了Any接口类型的变量,都能正常运行。
golang给我们提供了一个更为精简的没有方法的接口类型,即:
interface{}
根据之前的观点,任何类型都实现了没有方法的接口。上边的代码也可以修改成下边的样式:
package main
import (
"fmt"
)
func main() {
var fromInt interface{} = 10
var fromStr interface{} = "hello world"
var fromFloat interface{} = 3.14
fmt.Println(fromInt, fromStr, fromFloat)
}
interface{}类型的变量,可以使用任何类型的值来赋值,可以称之为万能类型。
接口组合
接口组合,就是在接口中引入另一个接口。接口被引入后,外层接口就接收了被引入接口的所有方法。示例代码如下:
type Reader interface {
Read() string
}
type Writer interface {
Write(str string)
}
type ReaderWriter interface {
Reader
Writer
}
Reader接口有一个方法Read, Writer接口中有一个方法Write, 而ReaderWriter组合了Reader和Writer接口,那么ReaderWriter接口就拥有了Read和Write方法。如果要实现ReaderWriter这个接口,就必须拥有Read和Write两个方法,缺一不可。
下边来定义一个类型实现ReaderWriter接口,示例代码如下:
type file struct {
}
func (r *file) Read() string {
return time.Now().Format("2006-01-02 15:04:05")
}
func (r *file) Write(str string) {
fmt.Println(str)
}
类型查询
有两个类型都实现了某一个接口,这两个类型的变量都可以被赋值给这个接口类型的变量。接口查询,就是根据这个接口类型变量,查找到赋值给它的那个变量的类型。之前在type关键字讲解中将结果使用.(type)实现类型查询的方法,在这了就不在累述,下边来介绍一下通过断言方式实现接口查询,断言查询的语法格式是:
ret, flag := obj.(dt)
obj表示接口类型变量,dt表示猜测的类型,flag就是猜测的结果。如果flag为true,表示obj由dt类型的变量赋值。如果flag为false,表示obj不是由dt类型变量赋值。不管flag是true还是false,ret都是之前给obj赋值的那个变量。
如下边示例代码:
package main
import (
"fmt"
)
type B interface {
Say(str string)
}
type A struct {
}
func (r A) Say(str string) {
fmt.Println(str, "A结构体:")
}
type C struct {
}
func (r C) Say(str string) {
fmt.Println(str, "C结构体:")
}
func main() {
// 将A类型变量赋值给B接口类型变量b
var b1 B = A{}
var b2 B = C{}
// 判断b1是否由A类型变量赋值
if b, ok := b1.(A); ok {
b.Say("b1 是")
} else {
b.Say("b1 不是")
}
// 判断b2是否由A类型变量赋值
if b, ok := b2.(A); ok {
b.Say("b2 是")
} else {
b.Say("b2 不是")
}
}
输出信息是:
b1 是 A结构体:
b2 不是 A结构体:
上边示例中,接口类型变量b1由A类型变量赋值, b2由C类型变量赋值。通过类型查询,查询这个接口变量是否由某个指定类型变量赋值。
接口查询
接口查询,就是在一个接口变量中,去判断那个把值赋给这个接口变量的对象究竟有没有实现另一个接口,这个所谓的另一个接口,就是我要查询的那个接口。
通过接口查询,我们来充分的挖掘这个对象的更多功能,请看这段示例代码:
package main
import (
"fmt"
)
type R interface {
Read()
}
type W interface {
Write(name string)
}
type RW interface {
R
W
}
type log struct {
name []string
r int
}
func (t *log) Read() {
if len(t.name) > t.r {
fmt.Println(t.name[t.r])
t.r++
} else {
fmt.Println("empty")
}
}
func (t *log) Write(name string) {
t.name = append(t.name, name)
fmt.Println("wirte success.", t.name)
}
func main() {
var w W = &log{}
w.Write("interface W")
if val, ok := w.(RW); ok {
val.Read()
}
}
上边代码中的R接口,声明了Read方法,W接口,声明了Write方法。RW接口通过组合R与W,相当于声明了Read方法与Write方法。
log类实现了上边的3个接口。在实例化log后,将对象赋值给R接口变量和W接口变量。那么问题就来了,R接口变量只能访问Read方法;W接口变量只能访问Write方法。虽然log类型的对象拥有Write和Read方法,但是在赋值给不同的接口变量后,log类型对象本身的方法被隐藏了。接口查询,首先获取W接口变量w赋值的那个对象,即log指针类型对象,然后查询log对象是否实现了接口RW。
语法格式如下:
val, ok := w.(RW)
如果w接口变量的赋值对象实现了RW接口,则ok为true,val为log对象,如果w接口变量的赋值对象没有实现RW接口,则ok为false,val为nil