16.接口
16.接口
1,概述
在Go语言中,接口是一个自定义类型,接口类型具体描述了一系列方法的集合。
接口类型是一种抽象的类型,它不会暴漏出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。
Go通过借口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为
。
- 同一个结构体可以实现多个接口
- 接口可以嵌套
- 关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同方式进行处理的才需要定义接口。不要为了接口而定义接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。
2,接口的使用
1,接口定义
type Humaner interface {
SayHi()
}
- 接口命名习惯以
er
结尾 - 接口只有方法声明,没有实现,没有数据字段
- 接口可以匿名嵌入其他接口,或嵌入到结构中
2,接口实现
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。
如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。
3,示例学习
1,接口的定义与实现
package main
import "fmt"
type Humaner interface {
sayhi()
}
type Student struct {
name string
id int
}
//Student 通过上边定义的接口实现了其方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
type Teacher struct {
addr string
group string
}
//Teacher 通过上边定义的接口实现了其方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.addr)
}
//Mystr 通过上边定义的接口实现了其方法
type Mystr string
func (tmp *Mystr) sayhi() {
fmt.Printf("Mystr[%s] sayhi\n", *tmp)
}
func main() {
//定义接口类型的变量
var i Humaner
//只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"erya", 666}
i = s
i.sayhi()
t := &Teacher{"bj", "go"}
i = t
i.sayhi()
var str Mystr = "hello go"
i = &str
i.sayhi()
}
2,多态的呈现
其一:
package main
import "fmt"
type Humaner interface {
sayhi()
}
type Student struct {
name string
id int
}
//Student 通过上边定义的接口实现了其方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
type Teacher struct {
addr string
group string
}
//Teacher 通过上边定义的接口实现了其方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %d] sayhi\n", tmp.addr, tmp.addr)
}
//Mystr 通过上边定义的接口实现了其方法
type Mystr string
func (tmp *Mystr) sayhi() {
fmt.Printf("Mystr[%s, %d] sayhi\n", *tmp)
}
func main() {
//定义接口类型的变量
var i Humaner
//只要实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值
s := &Student{"erya", 666}
i = s
i.sayhi()
t := &Teacher{"bj", "go"}
i = t
i.sayhi()
var str Mystr = "hello go"
i = &str
i.sayhi()
}
其二:
package main
import "fmt"
type Humaner interface {
sayhi()
}
type Student struct {
name string
id int
}
//Student 通过上边定义的接口实现了其方法
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
type Teacher struct {
addr string
group string
}
//Teacher 通过上边定义的接口实现了其方法
func (tmp *Teacher) sayhi() {
fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.addr)
}
//Mystr 通过上边定义的接口实现了其方法
type Mystr string
func (tmp *Mystr) sayhi() {
fmt.Printf("Mystr[%s] sayhi\n", *tmp)
}
//定义一个普通函数,函数的参数为接口类型
//只有一个函数,可以有不同表现,即为多态
func WhoSayHi(i Humaner) {
i.sayhi()
}
func main() {
s := &Student{"erya", 666}
t := &Teacher{"bj", "go"}
var str Mystr = "hello go"
//调用同一个函数,会有不同表现,多态
// WhoSayHi(s)
// WhoSayHi(t)
// WhoSayHi(&str)
//创建一个切片
x := make([]Humaner, 3)
x[0] = s
x[1] = t
x[2] = &str
//第一个返回下标,第二个返回下标对应的值
for _, i := range x {
i.sayhi()
}
}
3,接口的继承
package main
import "fmt"
type Humaner interface { //子集
sayhi()
}
type Personer interface { //超集
Humaner //匿名字段,继承了sayhi()
sing(lrc string)
}
type Student struct {
name string
id int
}
//Student 实现了sayhi()
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
func (tmp *Student) sing(lrc string) {
fmt.Println("Student在唱着: ", lrc)
}
func main() {
//定义一个接口类型的变量
var i Personer
s := &Student{"erya", 666}
i = s
i.sayhi()
i.sing("奔跑")
}
4,接口的转换
这个地方涉及到一个概念,就是超集可以转换为子集,而子集不能转换为超集,其实很容易理解,因为超集里边将会有子集当中未定义的东西。
package main
import "fmt"
type Humaner interface { //子集
sayhi()
}
type Personer interface { //超集
Humaner //匿名字段,继承了sayhi()
sing(lrc string)
}
type Student struct {
name string
id int
}
//Student 实现了sayhi()
func (tmp *Student) sayhi() {
fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}
func (tmp *Student) sing(lrc string) {
fmt.Println("Student在唱着: ", lrc)
}
func main() {
//超集可以转换为子集,而子集不能转换为超集
//其实很容易理解,因为超集里边将会有子集当中未定义的东西
var iPro Personer //超集
iPro = &Student{"erya", 666}
var i Humaner //子集
iPro = i //err
i = iPro //可以,超集可以转换为子集
i.sayhi()
}
5,空接口
空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口
,因此空接口可以存储任意类型的数值。它有点类似于C语言的void*类型。
var v1 interface{} = 1 // 将int类型赋值给interface{}
var v2 interface{} = "abc" // 将string类型赋值给interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ x int }{1}
var v5 interface{} = &struct{ x int }{1}
当函数可以(或者需要)接受任意的对象实例时,我们会将其声明为interface{},最典型的例子就是标准库fmt中print xxx系列的函数,例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
下边用一个实例来理解。
package main
import "fmt"
func main() {
//空接口类型,相当于万能类型,可以保存任意类型的值
var i interface{} = 1
fmt.Println("i = ", i)
i = "abc"
fmt.Println("i = ", i)
}
6,接口类型查询
我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
- comma-ok断言
- switch测试
1,comma-ok断言
Go语言里面有一个语法,可以直接判断是否是该类型的变量:value, ok = element.(T)
这里的value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。
如果element里面确实存储了T类型的数值,那么ok返回true,反之则返回false。
首先可以通过if判断来进行查询。
package main
import "fmt"
type Student struct {
name string
id int
}
func main() {
i := make([]interface{}, 3)
i[0] = 1
i[1] = "hello go"
i[2] = Student{"erya", 666}
//类型查询,也叫类型断言
//第一个返回下标,第二个返回下标对应的值,data分别是i[0],i[1],i[2]
for index, data := range i {
//第一个返回的是值,第二个返回判断结果的真假
if value, ok := data.(int); ok == true {
fmt.Printf("x[%d] 类型为int, 内容为 %d\n", index, value)
} else if value, ok := data.(string); ok == true {
fmt.Printf("x[%d] 类型为string, 内容为 %s\n", index, value)
} else if value, ok := data.(Student); ok == true {
fmt.Printf("x[%d] 类型为Student, 内容为name = %s, id = %d\n", index, value.name, value.id)
}
}
}
或者使用switch进行判断。
package main
import "fmt"
type Student struct {
name string
id int
}
func main() {
i := make([]interface{}, 3)
i[0] = 1
i[1] = "hello go"
i[2] = Student{"erya", 666}
//类型查询,也叫类型断言
for index, data := range i {
switch value := data.(type) {
case int:
fmt.Printf("x[%d] 类型为int, 内容为 %d\n", index, value)
case string:
fmt.Printf("x[%d] 类型为string, 内容为 %s\n", index, value)
case Student:
fmt.Printf("x[%d] 类型为Student, 内容为name = %s, id = %d\n", index, value.name, value.id)
}
}
}