golang中的接口使用
golang 中的接口和别的面向对象中的接口有很大的不同。
接口的定义
//定义一个接口,它有一个run 方法 type Runable interface { run() }
定义一个Runable 接口,它有一个方法,run() , 这个方法没有参数也没有返回值
结构体可以定义一个方法,如果某个结构体定义了run() 方法,则说明该结构体实现了 Runable 接口,并不像java 或者 python 中的类,在定义的时候在显示的说明继承自哪个接口。
接口可以有值
这个和大多数的面向对象语言有区别,它们的接口是不可以实例化的,但是Go中的接口是可以”实例”的
func main() { var r Runable fmt.Println(r) }
打印出来是nil, 也就是说,实例化接口得到的是一个空指针,空指针此时是不能直接调用接口方法的,如 r.run(),
之后如果想要将该指针赋值,也要指向一个实现了run() 方法的结构体变量
如果指向了实例,则可以调用run方法。
package main import "fmt" //定义一个接口,它有一个run 方法 type Runable interface { run() } type Person struct { name string age int } type Cat struct { name string } //实现了Runable接口的run方法 func (self *Person) run() { fmt.Println("Person run....") } func main() { var r Runable fmt.Println(r) // 这里会编译错误 //r.run() p := Person{"yyx", 18} c := Cat{"mimi"} r = &p //r = c r.run() // 这里会调用Person的run方法 }
上面代码在 r = c 时会出现编译错误
Cannot use 'c' (type Cat) as the type Runable Type does not implement 'Runable' as some methods are missing: run()
意思就是 Cat 类并没有实现 Runable 接口的 run() 方法
通过接口实现多态
多态在面向对象语言中非常重要,由于有了多态,才会发挥面向对象的优势,我们在定义一个函数时,参数可以使用接口类型,在真正调用该函数的时候,只要传入一个实现了该接口的结构体即可。
package main import "fmt" //定义一个接口,它有一个run 方法 type Runable interface { run() } type Person struct { name string age int } type Dog struct { name string } func (self *Person) run() { fmt.Println("Person run....") } func (self Dog) run() { fmt.Println("Dog run....") } // 定义一个test函数,参数为Runable 接口类型 func test(obj Runable) { obj.run() } func main() { p := Person{"yyx", 18} d := Dog{"wangwang"} // 将实现了Runable 接口的结构体用为参数,当调用相应的方法 test(&p) test(d) }
上面代码输出为
Person run.... Dog run....
上面代码中定义了一个test(obj Runable) 函数,参数为Runable 接口,在之后的调用中,只要传一个实现了Runable接口的结构体即可, test函数中调用了run() 方法,将会调用结构体相应的run方法。
结构体方法的接收者类型
结构体在实现接口方法的时候,接收者参数可以是值类型也可以是指针类型,这个是之后的调用过程中是有影响的
在上面的代码中
func (self *Person) run() { fmt.Println("Person run....") } func (self Dog) run() { fmt.Println("Dog run....") }
Person 使用的是指针类型,Dog 使用的是值类型,
那么在之后的test() 函数调用时
p := Person{"yyx", 18} d := Dog{"wangwang"} // 将实现了Runable 接口的结构体用为参数,当调用相应的方法 test(&p) test(d) Person 类型的就要传地址,Dog 类型的就可以直接传值。 另外,对于接口值也需要注意 var r Runable fmt.Println(r) p := Person{"yyx", 18} d := Dog{"wangwang"} r = &p r = d
r 在刚初始化的时候是nil, 如果实现方法接收者为指针类型,那么r也要指向结构体变量地址,如接收者为值类型,那么r 可以直接指向结构体变量。
接口变量的类型判断
如上面的代码定义,有一个Runable 接口,有两个结构体,Person 和 Dog , 如果某个时刻你想知道这个接口变量到底是哪个结构体时,可以使用接口变量类型判断
使用 v := varI.(T) 形式
func main() { var r Runable p := Person{"yyx", 18} d := Dog{"wangwang"} r = d dd := r.(Dog) fmt.Println(dd, "ddd") pp := r.(*Person) fmt.Println(pp, "pp") }
如果接口变量是T类型的话,则返回的v就是接口所指向的变量,如果不是T类型的话,则发生panic
上面的结果为
{wangwang} ddd panic: interface conversion: main.Runable is main.Dog, not *main.Person
使用 if v, ok := varI.(T); ok 形式
上面的代码会导致panic, 更加安全的方式上使用if 判断的形式
func main() { var r Runable p := Person{"yyx", 18} d := Dog{"wangwang"} r = d if person, ok := r.(*Person); ok{ fmt.Println(person, 222) }else { fmt.Println(person, 333) } if dog, ok := r.(Dog); ok{ fmt.Println(dog, 444) }else{ fmt.Println(dog, 555) } }
由于将r指向了Dog的变量d,所以上面的代码输出为
333 {wangwang} 444 type-switch 形式 func main() { var r Runable fmt.Println(r) p := Person{"yyx", 18} d := Dog{"wangwang"} r = d switch t:= r.(type) { case *Person: fmt.Printf("Type Person %T value %v \n", t,t) case Dog: fmt.Printf("Type Dog %T value %v \n", t,t) case nil: fmt.Println("t is nil....") default: fmt.Println("default case......") } }
case 中除了nil 和default 之外的结构体都需要实现接口方法,否则编译错误。
判断值是否实现了某个接口
如果想要判断某个值是否实现了某个接口, 可以使用v.(Inter)
func main() { var r Runable fmt.Println(r) p := Person{"yyx", 18} d := Dog{"wangwang"} // 判断某个值是否实现了Runable接口 r = &p if _, ok := r.(Runable); ok{ fmt.Println("p 实现了Runable接口") }else{ fmt.Println("p 没有实现Runable接口") } }
我的理解,这里的检查某个接口值是否实现了某个接口,相当于判断了该接口是否是nil, 当接口值想要指向某个变量时,如果那个变量没有实现接口方法,也不可能指向成功。
上面的代码,如果将 r = &p 注释掉,则OK 为false,此时只能说明nil 刚初始化,指向的是nil.