深入理解go语言的反射

介绍

反射就是在程序运行时获得对象的实际类型,从而进行相关的操作。在一些框架中经常使用到反射,需要使用反射来获取传递的数据结构。本文主要讲解一下 go 语言中反射如何使用。

类型和接口

在开始之前,需要想了解一下 go 语言中类型接口两个概念。go 语言是静态语言,对于每一个变量的实际类型,在编译时就已经确定,例如: int, byte, float。对与 iType, myType 分别为 int 和 myType 类型,虽然iType 和 myType 的潜在类型都是 int, 但是在没有类型转换之前是无法进行赋值操作的。

type myType int
var iType int
var myType MyType
复制代码

在 go 语言有一种非常特殊的类型叫接口类型,它表示一系列方法签名的集合。接口类型的变量可以存储任何非接口类型的变量,只要这个变量实现了对应接口类型定义的方法即可。我们以 go 语言中的 Reader 类型进行讲解, 自定义类型 MyReader, 实现 Read 接口, 则无论 MyReader 是否包含非接口类型的变量, 均可描述为接口类型 io.Reader。

「思考」为什么要有接口类型的变量呢?

// 定义一个接口类型 Reder, 包含的方法为 func Read(p []byte) (n int, err error)
type Reader interface {
    Read(p []byte) (n int, err error)
}

// 自定义类型
type MyReader struct {
   str string
}
// 实现接口类型 Reader 的方法
func (myReader *MyReader) Read(p []byte) (n int, err error) {
	return 0, nil
}
// 定义接口类型
var myReader io.Reader
// 无论 myReader 的值为什么,其类型均为 io.Reader
myReader = &MyReader{}
复制代码

在 go 语言中,我们经常看到空接口类型 interface{}, 我们可以将其看作一个不包含任何方法签名的接口类型。不包含任何方法就是包含任何方法,即任何类型都实现了空接口类型的方法,均可以被表示为 interface{}

// 空接口类型,可以描述任何类型
var i interface{}
// 均实现了空接口签名的方法
i = 9
i = myReader
复制代码

接口的表示

从上节中可以看到,接口类型和普通类型还是存在一定的差别,即只要实现接口中定义的方法就可以被描述为对应的接口类型。在 go 语言中是如何描述接口类型的变量呢?
在接口类型变量中,存储了两个值:分配给该变量的真实值,该变量的真实类型描述「非接口类型描述」。例如, 接口类型变量 myReader, 里面存储了值 temp, 类型 MyReader。

var myReader io.Reader
temp := &MyReader{}
myReader = temp
复制代码

此外,我们可以让 MyReader 实现 Writer 方法,那么我们就可以通过断言将接口类型变量 myReader 转换成接口类型 io.Writer。对于 myWriter 里面存储的的值为 temp, 类型 MyReader。

func (myReader *MyReader) Write(p []byte) (n int, err error) {
	return 0, nil
}
// 类型断言
myWriter := myReader.(io.Writer)
复制代码

反射的三大定律

  • 反射将接口值转换为反射对象
    在 go 语言中,通过 reflect.ValueOf(i interface{}), reflect.TypeOf(i interface{}) 分别获取接口变量 i 对应的 value 和 type。变量 x 先被转换为空接口类型 interface{}, reflect.ValueOf(x), reflect.Value(x) 将接口类型转化为反射对象 Value 和 Type。拿到变量的 Value 和 Type 之后,我们就可以拿到了变量的全部信息。
type MyInt int
var x MyInt = 7
// 7 main.MyInt
fmt.Println(reflect.ValueOf(x), reflect.TypeOf(x))
复制代码
  • 反射可以将反射对象转换为接口对象
    既然能将接口变量转换为反射对象,那就需要将反射对象转换为接口对象。例如,将变量 x 转换为接口类型 tempX, 将 tempX 转换为反射类型对象 v, 将 v 转换为接口类型 i, 最后将 i 转换为 MyInt 对象。
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
i := v.Interface()
var myInt MyInt = i.(MyInt)
复制代码
  • 反射对象必须是可修改的
    可以将指定类型的变量转换为接口类型,接口类型转换为反射类型,反射类型转换为接口类型,接口类型转换为指定类型。如果可以更新反射对象,就可以更新对应的变量。
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
v.SetInt(9)
fmt.Println(v.Interface())
-----------------------------
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
复制代码

无法进行赋值,必须使用指针修改指向的值。reflect.ValueOf(&x) 获取指针对应的反射对象 v,v.Elem() 获取指针指向的反射对象,v.SetInt(9) 更新指针指向的对象,v.interface() 获取对应的值。

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(&x)
v = v.Elem()
v.SetInt(9)
fmt.Println(v.Interface())
复制代码

更新结构体的值

type T struct { // 属性必须是可导出的
	Name string
	Age  int64
}

func main() {
    t := &T{Name: "test", Age: 19}
    v := reflect.ValueOf(t).Elem()
    typeOfT := v.Type()
    for i := 0; i < v.NumField(); i++ {
        f := v.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
                typeOfT.Field(i).Name, f.Type(), f.Interface())
    }
    //0: Name string = test
    //1: Age int64 = 19
    // 更新结构体的属性
    switch f.Kind() {
        case reflect.Int64:
                f.SetInt(99)
        case reflect.String:
                f.SetString("a")
        default:
                continue
    }
}
复制代码

欢迎添加微信公众号「洛小妍」交流学习