go interface 和 reflect

106 阅读6分钟

前言

go interface使得go语言可像java语言一样具有多态,reflect是go用于将interface进行反射处理的package,本文尝试以初学者的角度详细解释go的interface核reflect。

interface

下面一段代码是interface的一个简单示例,一个IPrint接口,具有Print()方法。其中结构体PrintA和PrintB都实现了IPrint接口。一个Print(p IPrint)方法传入一个IPrint接口,并调用接口的Print()方法。在主程序调用中,一个IPrint的变量p依次赋值为PrintA类型和PrintB类型,并实现了同一个函数不同的实现调用,这就是多态。

package main

import "fmt"

type IPrint接口,具有 interface {
   Print()
}

type PrintA struct {
}

func (p PrintA) Print()  {
   fmt.Println("A")
}

type PrintB struct {
}

func (p PrintB) Print()  {
   fmt.Println("B")
}

func Print(p IPrint)  {
   p.Print()
}

func main()  {
   var p IPrint
   p = PrintA{}
   Print(p)
   p = PrintB{}
   Print(p)
}

程序输出如下:
A
B

在go语言中还有一个0方法的接口 interface{},由于所有类型都实现了至少0个方法,因此所有类型都可以赋值给空接口变量。

var i interface{}
i = 1
i = PrintA{}
i = PrintB{}

interface实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,下面分别是空接口和带方法的接口是使用的数据结构:

//Eface是interface{}底层使用的数据结构。数据域中包含了一个void*指针,和一个类型结构体的指针。
struct Eface
{
    Type*    type;
    void*    data;
};

struct Type
{
    uintptr size;
    uint32 hash;
    uint8 _unused;
    uint8 align;
    uint8 fieldAlign;
    uint8 kind; // kind是一个枚举值,每种类型对应了一个编号
    Alg *alg;
    void *gc;
    String *string;
    UncommonType *x;  UncommonType是指向一个函数指针的数组,收集了这个类型的实现的所有方法。
    Type *ptrto;
};

//Iface 是带方法的 interface 底层使用的数据结构
struct Iface
{
    Itab*    tab;
    void*    data;
};

struct Itab
{
    InterfaceType*    inter;
    Type*    type;
    Itab*    link;
    int32    bad;
    int32    unused;
    void    (*fun[])(void);
};

可以使用类型断言将接口强制转换为具体的类型,如下代码所示:

func findType(p IPrint)  {
   switch x := p.(type) {
   case PrintA:
      fmt.Println("PrintA")
   case PrintB:
      fmt.Println("PrintB")
   default:
      fmt.Println(x, "not type matched")
   }
}

在一般开发中,由于开发者知道该接口有哪些具体类型,因此使用类型断言来的简单些,但如果不知道类型有哪些类型,类型断言将失效,需要借助reflect包具体的反射功能。

reflect

所谓反射,就是指在程序run-time获取interface{}变量的具体类型和具体值。reflect包提供了reflect.TypeOf()和reflect.ValueOf()也获取具体类型和具体值。其中具体类型和具体值分别使用reflect.Type和reflect.Value数据结构表示。示例程序如下:

package main

import(
   "fmt"
   "reflect"
)

type order struct{
   ordId int
   customerId int
}

func query(q interface{}) {
   t := reflect.TypeOf(q)
   // 具体类型的每个字段
   for i :=0; i<t.NumField(); i++{
      fmt.Println(fmt.Sprintf("field %s, type %s", t.Field(i).Name, t.Field(i).Type))
   }

   // 具体每个字段的值
   v := reflect.ValueOf(q)
   for i := 0;i<v.NumField();i++{
      fmt.Println(fmt.Sprintf("type %s, value %d", v.Field(i).Kind(), v.Field(i).Int()))
   }

}

func main(){
   o := order{
      ordId: 456,
      customerId: 56,
   }
   query(o)
}

程序输出如下:
field ordId, type int
field customerId, type int
type int, value 456
type int, value 56

其中 reflect.Type 表示具体的类型,reflect.Value表示具体的值,底层源码如下:

// Type is the representation of a Go type.
type Type interface {
    // 变量的内存对齐,返回 rtype.align
    Align() int

    // struct 字段的内存对齐,返回 rtype.fieldAlign
    FieldAlign() int

    // 根据传入的 i,返回方法实例,表示类型的第 i 个方法
    Method(int) Method

    // 根据名字返回方法实例,这个比较常用
    MethodByName(string) (Method, bool)

    // 返回类型方法集中可导出的方法的数量
    NumMethod() int

    // 只返回类型名,不含包名
    Name() string

    // 返回导入路径,即 import 路径
    PkgPath() string

    // 返回 rtype.size 即类型大小,单位是字节数
    Size() uintptr

    // 返回类型名字,实际就是 PkgPath() + Name()
    String() string

    // 返回 rtype.kind,描述一种基础类型
    Kind() Kind

    // 检查当前类型有没有实现接口 u
    Implements(u Type) bool

    // 检查当前类型能不能赋值给接口 u
    AssignableTo(u Type) bool

    // 检查当前类型能不能转换成接口 u 类型
    ConvertibleTo(u Type) bool

    // 检查当前类型能不能做比较运算,其实就是看这个类型底层有没有绑定 typeAlg 的 equal 方法。
    // 打住!不要去搜 typeAlg 是什么,不然你会陷进去的!先把本文看完。
    Comparable() bool

    // 返回类型的位大小,但不是所有类型都能调这个方法,不能调的会 panic
    Bits() int

    // 返回 channel 类型的方向,如果不是 channel,会 panic
    ChanDir() ChanDir

    // 返回函数类型的最后一个参数是不是可变数量的,"..." 就这样的,同样,如果不是函数类型,会 panic
    IsVariadic() bool

    // 返回所包含元素的类型,只有 Array, Chan, Map, Ptr, Slice 这些才能调,其他类型会 panic。
    // 这不是废话吗。。其他类型也没有包含元素一说。
    Elem() Type

    // 返回 struct 类型的第 i 个字段,不是 struct 会 panic,i 越界也会 panic
    Field(i int) StructField

    // 跟上边一样,不过是嵌套调用的,比如 [1, 2] 就是说返回当前 struct 的第1个struct 的第2个字段,适用于 struct 本身嵌套的类型
    FieldByIndex(index []int) StructField

    // 按名字找 struct 字段,第二个返回值 ok 表示有没有
    FieldByName(name string) (StructField, bool)

    // 按函数名找 struct 字段,因为 struct 里也可能有类型是 func 的嘛
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    
    // 返回函数第 i 个参数的类型,不是 func 会 panic
    In(i int) Type

    // 返回 map 的 key 的类型,不是 map 会 panic
    Key() Type

    // 返回 array 的长度,不是 array 会 panic
    Len() int

    // 返回 struct 字段数量,不是 struct 会 panic
    NumField() int

    // 返回函数的参数数量,不是 func 会 panic
    NumIn() int

    // 返回函数的返回值数量,不是 func 会 panic
    NumOut() int

    // 返回函数第 i 个返回值的类型,不是 func 会 panic
    Out(i int) Type
}

// Value is the reflection interface to a Go value.
type Value struct {
    // 反射出来此值的类型,这 typ 是未导出的,从外部调不到 Type 接口的方法
    // rtype是上面Type的一个具体实现的结构体
    typ *rtype

    // 数据形式的指针值
    ptr unsafe.Pointer

    // 保存元数据
    flag
}

一个带修改值的反射示例:

package main

import(
   "fmt"
   "reflect"
)

type T struct {
   A int
   B string
}

func main(){
   t := T{23,"hello world"}
   s := reflect.ValueOf(&t).Elem()
   s.Field(0).SetInt(22)
   s.Field(1).SetString("XXOO")
   typeOfT := s.Type()
   for i:=0; i<s.NumField(); i++ {
      f := s.Field(i)
      fmt.Printf("%d: %s %s = %v\n", i,typeOfT.Field(i).Name, f.Type(), f.Interface())
   }
}

进阶

看开源的 json.Unmarshal(data []byte, v interface{}) 方法,可以对reflect的用法更加熟悉。

参考

Go Data Structures: Interfaces
interface
golang中的reflect(反射)
Go Reflect 高级实践
反射 - reflect.ValueOf()