Go语言反射使用技巧

137 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第26天,点击查看活动详情

Go 语言使用其自带的 reflect 包来实现反射。其反射机制就是程序在运行过程中,可以动态地调用对象的方法和属性。

我们知道,一个变量由类型和值两部分组成,类型又包括静态类型具体类型。静态类型就是在编码时可见的类型(int、string等),而具体类型是在程序运行时系统所见的类型。

interface

interface(接口) 类型是一种特殊的类型,interface 用来表示一组方法集合,所有实现该方法集合的类型都可以被认为是实现了该接口。所以空 interface 类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口

反射有两种类型 reflect.Valuereflect.Type ,分别表示变量的值和类型,并且提供了两个函数 reflect.ValueOfreflect.TypeOf 分别获取任意对象的 reflect.Valuereflect.Type

使用示例:

package main

import (
    "fmt"
    "reflect"
)

func main(){
    var x float64 = 3.4
    t := reflect.TypeOf(x)
    fmt.Println("type:", t)

    v := reflect.ValueOf(x)
    fmt.Println("value", v)
}

//运行结果
//type: float64
//value 3.4

reflect

  1. reflect.Value

reflect.ValueOf() 定义如下:

func ValueOf(i interface{}) Value {...}

ValueOf() 函数用来获取输入参数接口中的数据的值。如果接口为空,则返回 0。

Value 类型为:

type Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}

示例,修改 struct 结构体字段的值:

func main() {
   p := person{Name: "微客鸟窝",Age: 18}
   pv:=reflect.ValueOf(&p)
   pv.Elem().Field(0).SetString("无尘")
   fmt.Println(p)
}
type person struct {
   Name string
   Age int
}
  1. reflect.Type

reflect.TypeOf() 定义如下:

func TypeOf(i interface{}) Type {...}

TypeOf() 函数用来动态获取输入参数接口中的值的类型,如果接口为空,则返回 nil。

reflect.Type 实际上是一个接口,定义了很多方法来获取类型相关的信息:

type Type interface {

   Implements(u Type) bool //方法用于判断是否实现了接口 u;
   AssignableTo(u Type) bool //方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;
   ConvertibleTo(u Type) bool //方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;
   Comparable() bool //方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。

   //以下这些方法和Value结构体的功能相同
   Kind() Kind

   Method(int) Method
   MethodByName(string) (Method, bool)
   NumMethod() int
   Elem() Type
   Field(i int) StructField
   FieldByIndex(index []int) StructField
   FieldByName(name string) (StructField, bool)
   FieldByNameFunc(match func(string) bool) (StructField, bool)
   NumField() int
}

示例,遍历结构体的字段和方法:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	p := person{Name: "微客鸟窝", Age: 18}
	pt := reflect.TypeOf(p)
	//遍历person的字段
	for i := 0; i < pt.NumField(); i++ {
		fmt.Println("字段:", pt.Field(i).Name)
	}
	//遍历person的方法
	for i := 0; i < pt.NumMethod(); i++ {
		fmt.Println("方法:", pt.Method(i).Name)
	}
}

type person struct {
	Name string
	Age  int
}

func (p person) String() string{
	return fmt.Sprintf("Name is %s,Age is %d",p.Name,p.Age)
}

反射3大法则

  1. 反射可以将 interface 类型变量转换成反射对象 通过 reflect 包的一些函数,可以把接口转换为反射定义的对象。
  • reflect.ValueOf() 获取某个变量的值
  • reflect.TypeOf() 获取某个变量的静态类型
  • reflect.Value.Kind() 获取变量值的底层类型,底层类型可能为int/float/struct/slice等
  • reflect.Value.Type() 获取变量值的类型,等同于reflect.TypeOf()
  1. 反射可以将反射对象还原成 interface 对象
package main

import (
    "fmt"
    "reflect"
)

func main(){
    var x float64 = 3.4
    v := reflect.ValueOf(x) //v is reflext.Value
    var y float64 = v.Interface().(float64)
    fmt.Println("value", y)
}

//运行结果:
//value 3.4

对象 x 转换成反射对象 v,v 又通过 Interface() 接口转换成了 interface 对象,interface 对象通过.(float64)类型断言获取 float64 类型的值。 断言格式为:s = x.(T),意思是如果 x 所持有的元素如果同样实现了 T 接口,那么就把值传递给 s。

  1. 反射对象可修改,value值必须是可设置的

当使用 TypeOf() 和 ValueOf() 时,如果传递的不是接口变量的指针,那么反射里的变量值是一个副本值,对反射对象进行修改时,并不能修改真实的值。

错误示例:

package main

import (
    "reflect"
)

func main(){
    var x float64 = 3.4
    v := reflect.ValueOf(x) //v is reflext.Value
    v.SetFloat(6.6) //Error
}

上面的程序会发生 panic ,因为 v 是不可修改的。

  • 传入 reflect.ValueOf() 函数的其实是 x 的副本值,而并非 x 本身。所以通过 v 修改其值是无法影响 x 的,所以会报错。
  • 若要修改,我们可以在 ValueOf()中传入 x 的地址,此时 v 代表的是指针地址,如何通过指针地址 v 修改 x 的值呢?
  • 使用 reflect.Value 的 Elem() 方法,可以获得指针指向的 value 。

示例:

package main

import (
    "fmt"
    "reflect"
)

func main(){
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    v.Elem().SetFloat(6.6)
    fmt.Println("x :", v.Elem().Interface())
}

//运行结果
//x : 6.6