Go语言 interface 理解 & 与反射的关系

理解 interface

1、interface 是一种类型

准确来说,interface 是带有一组方法的一种类型,这些方法定义了 interface 的行为。如果一个类型实现了一个 interface 中所有方法,则该类型实现了该 interface。又因为 go 允许不带任何方法的interface存在,这种interface成为空interface。所以所有类型都实现了 empty interface,因为任何一种类型至少实现了 0 个方法。

go 没有显式的关键字用来实现 interface,只需要实现 interface 包含的方法即可。

2、空 interface

interface{} 是一个空的 interface 类型,前面说到基本上所有的类型都可被空 interface 接收,因此如果定义一个函数参数是 interface{} 类型,这个函数应该可以接受任何类型作为它的参数。

func AnyType(i interface{}) {
   fmt.Println(i)
}

func TestInterface(t *testing.T) {
   var a int64 = 1
   var b int8 = 2
   var c = 3.01
   var s = "str"

   AnyType(a)
   AnyType(b)
   AnyType(c)
   AnyType(s)
}

2.1 底层结构

空接口的底层结构记录在反射包中 reflect/value.go

可以看到空interface的结构体中有一个类型描述字段和一个包含着值的字段:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}
  • word指向值的地址
  • typ保存类型信息

2.2 interface{} 在反射中的运用

我们知道在使用反射时最重要的两个API就是 reflect.TypeOf 和 reflect.ValueOf ,而这两个方法的入参类型都是使用 interface{} 来接收任意类型的参数的,附上源码:

func TypeOf(i interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i))
   return toType(eface.typ)
}

因为 interface{} 的底层结构就是 emptyInterface ,并且在接收原有类型转化为 interface{} 时系统底层就会初始化 emptyInterface 结构体,所以通过强转就能够获取到它对应的 emptyInterface 结构体,以及 typ 或 word 成员变量。

rtype结构体定义:

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr // number of bytes in the type that can contain pointers
   hash       uint32  // hash of type; avoids computation in hash tables
   tflag      tflag   // extra type information flags
   align      uint8   // alignment of variable with this type
   fieldAlign uint8   // alignment of struct field with this type
   kind       uint8   // enumeration for C
   // function for comparing objects of this type
   // (ptr to object A, ptr to object B) -> ==?
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte   // garbage collection data
   str       nameOff // string form
   ptrToThis typeOff // type for pointer to this type, may be zero
}

拿到类型信息后,反射就可以通过解析这些信息确定对象的类型、获取属性信息、并且可以修改相关的属性,以及调用包含的方法等等操作。

这些操作的方法都定义在 reflect.Type 和 reflect.Value 中。所有类型的相关方法都统一定义在了这两个接口当中(参数统一转为 interface{} 接收的缘故),因此在调用每个方法时都会对类型进行判断,若非法则会抛出 panic 异常。

Kind 的定义如下:

type Kind uint

const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

reflect.Type 接口的方法汇总如下(都是与类型相关的信息):

type Type interface {
    // Kind返回该接口的具体分类
    Kind() Kind
    // Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
    Name() string
    // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
    PkgPath() string
    // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
    // 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
    String() string
    // 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
    Size() uintptr
    // 返回当从内存中申请一个该类型值时,会对齐的字节数
    Align() int
    // 返回当该类型作为结构体的字段时,会对齐的字节数
    FieldAlign() int
    // 如果该类型实现了u代表的接口,会返回真
    Implements(u Type) bool
    // 如果该类型的值可以直接赋值给u代表的类型,返回真
    AssignableTo(u Type) bool
    // 如该类型的值可以转换为u代表的类型,返回真
    ConvertibleTo(u Type) bool
    // 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
    Bits() int
    // 返回array类型的长度,如非数组类型将panic
    Len() int
    // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
    Elem() Type
    // 返回map类型的键的类型。如非映射类型将panic
    Key() Type
    // 返回一个channel类型的方向,如非通道类型将会panic
    ChanDir() ChanDir
    // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
    NumField() int
    // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
    Field(i int) StructField
    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
    // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
    // 如非函数类型将panic
    IsVariadic() bool
    // 返回func类型的参数个数,如果不是函数,将会panic
    NumIn() int
    // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
    In(i int) Type
    // 返回func类型的返回值个数,如果不是函数,将会panic
    NumOut() int
    // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
    Out(i int) Type
    // 返回该类型的方法集中方法的数目
    // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
    // 匿名字段导致的歧义方法会滤除
    NumMethod() int
    // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    Method(int) Method
    // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    MethodByName(string) (Method, bool)
    // 内含隐藏或非导出方法
}

2.3 举例说明

使用 reflect.Type.NumField 方法的底层实现作为例子,源码如下:

func (t *rtype) NumField() int {
   if t.Kind() != Struct {
      panic("reflect: NumField of non-struct type " + t.String())
   }
   tt := (*structType)(unsafe.Pointer(t))
   return len(tt.fields)
}

上文提过 interface{} 类型的数据结构是 emptyStruct , 其中包含了 rtype 和 word 两个字段,rtype 携带了所有类型的字段信息。所以 reflect.Type 接口的方法都是由 rtype 结构体实现的,然后通过 rtype 结构体中的字段信息进行解析。

步骤:

1. 通过 t.kind 字段得到 Kind 类型,判断是否为结构体类型

func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }

2. 若是,将 rtype 强转为 structType 类型(底层会通过 tflag 进行映射转换)

// structType represents a struct type.
type structType struct {
   rtype
   pkgPath name
   fields  []structField // sorted by offset
}

a := reflect.TypeOf(i) //int
b := reflect.TypeOf(j) //int
a == b
&a  != &b

3. 返回字段 fields 数量

3、判断 interface 的类型

3.1 判断 interface{} 所属类型

3.1.1 直接断言

val, ok := unknow.(string)

如果返回 ok 为true,则变量 unknown 为 string 类型,同时返回一个 val 存储 string 类型的值。

如果我们确定 unknown 为 string 类型,也可以不返回 ok 变量,直接强转获取其值:

val := unknow.(string)

但是使用这种方法有一定的风险,如果不是 string 类型,会发生 panic:

panic: interface conversion: interface {} is int, not string

3.1.2 使用反射

获取反射类型对象使用reflect.TypeOf,获取反射值对象使用reflect.ValueOf,具体使用方法:

typeRef = reflect.TypeOf(unknow)
valueRef = reflect.ValueOf(unknow)

3.1.3 type 关键字判断(只适用于switch)

switch unknow.(type){
    case string:
        //string类型
    case int:
        //int类型
    default:
}

3.2 判断 interface 接口的实现者

3.2.1 类型断言

当一个接口有多个实现时,也可以通过类型断言的方式来判断:

type I interface {    
    GetA() int
    SetA(int)
}

type S struct {
    A int
}

func(s S) GetA()int {
    return s.A
}

func(s *S) SetA(a int) {
    s.A = a
}

func f(i I){
   if t, ok := i.(*S); ok {
       // t.GetA()
   }
}

3.2.2 type 关键字判断

switch t := i.(type) {
case *S:
    // 
case *R:
    // 
default:
    // 
}

3.2.3 interface 接口的类型断言必须是实现者之一

前文说了 interface{} 可以接收任意类型的对象,是因为空接口没有方法,因此任意类型的对象都实现了空接口,故皆可赋给 interface{} 。但是 interface 接口类型有着确定的类型,所以只能接收实现了这个接口的对象,类型断言也是如此。

反例:

type I interface {    
    GetA() int
    SetA(int)
}

func f(i I){
    switch t := i.(type) {
    case *S:
        // 
    case *int:
        // 
    }
}

将会发生编译错误:

jiaqi.com/reflectDemo [jiaqi.com/reflectDemo.test] 

./main_test.go:34:2: impossible type switch case: i (type I) cannot have dynamic type *int (missing GetA method)

 

4、interface 实现者的 receiver

先来看一段代码:

type I interface {    
    GetA() int
    SetA(int)
}

type S struct {
    A int
}

func(s S) GetA()int {
    return s.A
}

func(s *S) SetA(a int) {
    s.A = a
}

func f(i I){
   if t, ok := i.(*S); ok {
       // t.GetA()
   }
}

func main() {
    s := S{}
    f(s)
}

Q:这段代码是否能正常运行?

A:不能,并且编译都不通过。

原因:S 中 setA 方法的 receiver 是个 pointer *S 。传给 f 的只是 S 的一份值拷贝并非指针(go 中函数都是按值传递)。因此使用 f(s)的形式调用,在进行 s 的拷贝到 I 的转换时,s 的拷贝不满足 SetA 方法的 receiver 是个 pointer。因此报错 does not implement 'I'

如何解决上述问题? 

func(s *S) SetA(a int)

改为👇🏻

func(s S) SetA(a int)

 这样可否?

可以发现编译的报错消失了,程序可以正常运行了!

我们来执行一下👇🏻

type I interface {
    GetA() int
    SetA(a int)
}

type S struct {
    A int
}

func(s S) GetA() int {
    return s.A
}

func(s S) SetA(a int) {
    s.A = a
}

func f(i I) {
    i.SetA(10)
    fmt.Println(i.GetA())
}

func TestReflect(t *testing.T) {
    s := S{}
    f(s)
    f(&s)
}

输出:

0

导致这个问题的原因是 I 的实现者 S 的方法都是 value receiver (值调用),对于 receiver 是 value 的 method,任何在 method 内部对 value 做出的改变都不影响调用者看到的 value,因为 value 只是一份拷贝,go 将无从得知 value 的原始值是什么,这就是按值传递。

如果是按 pointer 调用,go 会自动进行转换,因为指针指向的是原有对象的地址,go 会把指针进行隐式转换得到 value,但反过来则不行

5、interface 类型的隐式转换

我们可以通过一个例子再理解一下 interface{} ,下面的代码在 main 函数中初始化了一个 *TestStruct 类型的变量,由于指针的零值是 nil,所以变量 s 在初始化之后也是 nil

package main
import "fmt"

type TestStruct struct{}

func NilOrNot(v interface{}) bool {
    return v == nil
}

func main() {
    var s *TestStruct
    fmt.Println(s == nil)      // #=> true
    fmt.Println(NilOrNot(s))   // #=> false
    
    var i interface{}
    fmt.Println(NilOrNot(i))   // #=> true
}

执行结果:

true
false
true

出现上述现象的原因是 —— 调用 NilOrNot 函数时发生了隐式的类型转换。在类型转换时,*TestStruct 类型会转换成 interface{} 类型,转换后的变量不仅包含变量的值信息,还包含变量的类型信息 TestStruct,所以转换后的变量与 nil 不相等(还记得 interface{} 的底层数据结构是 emptyStruct 吗,包含两个成员变量,一个存储类型信息的 typ,另一个存储值信息)。

16行代码输出为 true 的原因是 interface{} -> interface{} 类型的传参,无需进行隐式转换,因此是一个 nil 的 interface{} 。

 6、reflect.Value.Kind 何时为 Interface

var i I = S{}
var ii interface{} = &i
typeOfI := reflect.TypeOf(ii).Elem()
fmt.Println("typeOfI Name:", typeOfI.Name())
fmt.Printf("typeOfI kind is Interface %t\n", typeOfI.Kind() == reflect.Interface)

valueOfI := reflect.ValueOf(ii)
fmt.Println(valueOfI.Elem().Kind())
fmt.Println(valueOfI.Elem().NumMethod())
fmt.Println(valueOfI.Elem().Elem().Kind())
fmt.Println(valueOfI.Elem().Elem().NumMethod())

输出:

typeOfI Name: I
typeOfI kind is Interface true
interface
2
struct
3

另一种方式,直接对 nil 进行强转:

reflect.TypeOf((*<interface>)(nil)).Elem()

 可以参考一下下面这篇文章:

Golang: reflect.Value.Kind何时为Interface - 知乎什么是反射?反射提供了一种运行时能对对象增删查改的方法. 换句话说,当函数参数的interface{}时,提供了一种访问原来的类型和值的方法. 这与switch type类似,但是switch只能对type进行判断,而你根本不知道会传进来…https://zhuanlan.zhihu.com/p/352312168

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值