美文网首页
golang reflect反射(二):interface接口的

golang reflect反射(二):interface接口的

作者: 陈卧虫 | 来源:发表于2019-03-31 15:44 被阅读0次

    什么是接口(interface)

    接口(interface)首先来说它是一种数据类型,里面存的数据是一组方法的集合,它只关心所包含的方法,不关心属性,也就是说属性是被它所忽略掉的。只要一个对象实现了接口定义的所有方法,那么这个对象就实现了这个接口。有点绕,举个例子:

    // 定义一个接口,里面有一个Print方法
    type MyInterface interface{
        Print()
    }
    
    // 定义一个结构体
    type MyStruct struct {   
    }
    
    // 实现Print方法
    func (me MyStruct) Print() {
        doSomething()
    }
    
    // 定义一个参数为MyInterface接口的函数
    func TestFunc(x MyInterface) {
        fmt.Println(x)
    }
    
    func main() {
        var me MyStruct
        // 因为MyStruct实现了MyInterface定义的所有方法,它就实现了MyInterface接口
        TestFunc(me)  //value
        
        var y MyInterface // 定义MyInterface接口
        var m MyStruct      
        
        y = m    // 因为MyStruct实现了MyInterface定义的所有方法,所以可以转换
    }
    

    从上面的例子可以看出,接口重点是方法,只要实现了定义的方法就可以调用,换一种数据类型仍然可以。

    // 定义一个接口
    type MyInterface interface{
        Print()
    }
    // 定义一个新的数据类型
    type myStr string
    // 实现Print方法
    func (s myStr) Print() {
        doSomething2()
    }
    
    func TestFunc(x MyInterface) {
        fmt.Println(x)
    }
    
    func main() {
        var s myStr
        // 它也可以被传入当做参数
        TestFunc(s)
    }
    

    现在应该有所理解interface的重点了,interface其实就是一种抽象类型,它是针对具体的类型,如int、map、slice或者自己定义的类型。具体类型是有具体的值,具体的类型,具体的方法的实现。但接口只是定义包含的方法,这些方法并不是由接口直接实现的,而是通过用户定义的类型来实现该方法,比如上面的例子中的MyStruct,myStr。

    存在既有道理,接口的优势就是通用,

    这个像是动态语言里的“鸭子类型”,一个对象只要”看起来像鸭子,走起来像鸭子“,那么它就可以被看成是鸭子。

    // 定义一个鸭子接口
    type Duck interface{
        Walk()
    }
    
    // 定义一个猪结构体
    type Pig struct {
    }
    // 实现Walk方法
    func (p Pig) Walk() {
        fmt.Println("pig walk")
    }
    
    // 定义一个狗结构体
    type Dog struct {
    }
    // 实现Walk方法
    func (d Dog) Walk() {
        fmt.Println("dog walk")
    }
    
    // 再定义一个必须传入鸭子作为参数的函数
    func DuckWalk(x Duck) {
        x.Walk()
    }
    
    func main() {
        var p Pig 
        var d Dog
    
        // 猪和够都可以被传入当做参数
        DuckWalk(p)   //正常
        DuckWalk(d)   //正常
    }
    

    这就是鸭子类型的优点,只要实现了定义的方法就能够调用,并不要考虑具体的方法实现是否一样。

    一个函数的传入参数如果被规定为一种具体类型,那么你就只能传入该类型的数据作为参数。再定义一个函数PigWalk,参数是Pig结构体类型:

    func PigWalk(p Pig)  {
        p.Walk()
    }
    

    如果你传入类型不是Pig, 就会报错:

    func main() {
        var d Dog
    
        PigWalk(d)
    
    }
    
    # command-line-arguments
    .\exe_time.go:37:9: cannot use d (type Dog) as type Pig in argument to PigWalk
    

    判断interface存储的变量是哪种类型

    一个对象转换成一个接口时,会失去它原来的类型,go提供了一种判断方式,断言:value, ok := em.(T)em 是 interface 类型的变量,T代表要断言的类型,value 是 interface 变量存储的值,ok 是 bool 类型表示此次言断是否成功

    var i interface{}
    var s string
    // 将s转换为一个空接口类型
    i = s
    
    if v, ok := i.(string); ok {
        fmt.Println(i, "is string type")
    } else {
        fmt.Println(i, "isn't string type")
    }
    

    ok是true表示i存储的是string类型,false则不是,这就是类型言断(Type assertions)

    如果需要区分多种类型,可以使用switch断言,能够一次性区分多种类型,但是只能在switch中使用:

    switch t := i.(type) {
    case string:
        fmt.Println("i store string", t)
    case int:
        fmt.Println("i store int", t)
    }
    

    空接口

    不带任何方法的interface就是一个空接口:empty interface

    type empty interface{}
    

    因为空接口不带任何方法,那就是它没有要求 ,既然没有要求那么所有的方法都可以啦,因此所有的类型实现了空接口。

    举例:我们常用的fmt.println()函数参数就是空接口,任何类型都可以传入:

    func Println(a ...interface{}) (n int, err error) {
        return Fprintln(os.Stdout, a...)
    }
    

    既然empty interface可以接受任何类型的参数,空接口的slice([]interface{})是否也可以接受任何类型的slice呢?试一下吧

    func printSlice(ins []interface{}) { 
        for _, in := range ins {
            fmt.Println(in)
        }
    }
    
    func main(){
        names := []string{"chen", "wo", "chong"}
        printSlice(names)
    }
    
    

    结果:

    # command-line-arguments
    .\test_interface_slice.go:13:12: cannot use names (type []string) as type []interface {} in argument to printSlice
    

    报错了,行不通,说明没有帮助我们自动把 slice 转换成 interface{} 类型的 slice,所以出错了。go 不会对 类型是interface{} 的 slice 进行转换 。为什么 go 不帮我们自动转换,一开始我也很好奇,最后终于在 go 的 wiki 中找到了答案 https://github.com/golang/go/wiki/InterfaceSlice 大意是 interface{} 会占用两个字长的存储空间,一个是自身的 methods 数据,一个是指向其存储值的指针,也就是 interface 变量存储的值,因而 slice []interface{} 其长度是固定的N*2,但是 []T 的长度是N*sizeof(T),两种 slice 实际存储值的大小是有区别的(文中只介绍两种 slice 的不同,至于为什么不能转换猜测可能是 runtime 转换代价比较大)。

    但是我们可以手动进行转换来达到我们的目的。

    var dataSlice []int = []int{1,2,3,4,5,6}
    var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) // 创建该长度的[]interface{}
    // 手动遍历
    for i, d := range dataSlice {
        interfaceSlice[i] = d
    }
    

    接口的接收者

    什么是接收者,在实现一个接口时,一个对象必须要实现接口提供的所有方法,而实现了所有方法的对象就可以称为方法的接收者( receiver )。

    func main() {
        
        var a animal
        
        var p pig
        a=p
        a.Show()
        
        //使用另外一个类型赋值
        var d dog
        a=d
        a.Show()
    }
    
    type animal interface {
        Show()
    }
    
    type pig int
    type dog int
    
    func (p pig) Show(){
        fmt.Println("papapa")
    }
    
    func (d dog) Show(){
        fmt.Println("wanwan")
    }
    

    实现方法的时候,可以通过对象的指针实现,也可以通过对象的值来实现,所以方法的接受者有两种,指针接收者(pointer receiver),值接收者(value receiver)。

    type Sheep int
    // 值接收者
    func (s Sheep) Show(){
        fmt.Println("mi-1")
    }
    // 指针接收者
    func (s *Sheep) Show(){
        fmt.Println("mi-2")
    }
    

    它们两个还是有区别的,

    如果对象通过value实现了该方法, 那就是这个对象的值实现了这个接口;

    如果是对象通过pointer实现了该方法,那就是这个对象的指针实现了这个接口;

    receiver是pointer

    type animal interface {
        Show()
    }
    
    type bird int
    // 指针接收者
    func (b *bird) Show() {
        fmt.Println("bird")
    }
    
    func ShowFunc(a animal)  {
        a.Show()
    }
    

    用对象的pointer调用, 结果正常

    func main() {
        var b bird
        ShowFunc(&b) 
    }
    

    但用对象的value调用,就会报错

    ShowFunc(b) 
    
    # command-line-arguments
    .\haha.go:7:10: cannot use b (type bird) as type animal in argument to ShowFunc:
        bird does not implement animal (Show method has pointer receiver)
    

    receiver是value

    type animal interface {
        Show()
    }
    
    type cat int
    // 指针接收者
    func (c cat) Show() {
        fmt.Println("cat")
    }
    
    func ShowFunc(a animal)  {
        a.Show()
    }
    
    func main() {
        var c cat
        ShowFunc(c) // value
        ShowFunc(&c) // pointer
    }
    

    值接收者( value receiver)实现接口,无论是 pointer 还是 value 都可以正确执行。

    为什么呢?

    在go执行过程中,如果是按 pointer 调用,go 会自动进行转换,因为有了pointer 总是能得到指针指向的value 是什么;如果是 value 调用,go 将无从得知 value 的原始值是什么,因为 value 是份拷贝,它在内存里的地址已经变化了。go 会把指针进行隐式转换得到 value,但反过来则不行

    通过这个例子我们可以得出结论:

    Methods Receivers Values
    (t T) T and *T
    (t *T) *T

    实体类型以值接收者实现接口的时候,不管是实体类型的值,还是实体类型值的指针,都实现了该接口

    实体类型以指针接收者实现接口的时候,只有指向这个类型的指针才被认为实现了该接口。

    其次我们我们以实体类型是值还是指针的角度看。

    Values Methods Receivers
    T (t T)
    *T (t T) and (t *T)

    上面的表格可以解读为:类型的值只能实现值接收者的接口;指向类型的指针,既可以实现值接收者的接口,也可以实现指针接收者的接口。

    如果觉得对您有所帮助的话,点个赞呗~,让我能够有写下去的动力。

    相关文章

      网友评论

          本文标题:golang reflect反射(二):interface接口的

          本文链接:https://www.haomeiwen.com/subject/dqcubqtx.html