Golang反射入门

88 阅读4分钟

反射是什么

反射允许程序在运行时访问和修改程序运行时信息,比如:获取变量的类型信息、值、方法,修改变量的值,调用方法等等,反射的功能很强大,但反射是把双刃剑,代码可读性极差。
Go反射没有Java反射功能完备,Java反射还能创建对象。

Go语言反射

Go语言的反射API都定义在reflect包中,Go语言反射定义了两个核心类型

  1. Type
  2. Value

通过Type和Value两个类型,就能访问变量的类型信息和值


image.png

实操

先定义好类型

type PFloat64 float64

type Cat struct {
   Color string
}

type Learn interface {
   Study()
   DoHomework(hour int)
}

// Class 表示班级
type Class struct {
   Num    int // 班级号
   Amount int // 班级人数
}

func (c Class) getPersonAmount() int {
   return c.Amount
}

type Student struct {
   Name   string `json:"name"`
   Age    int    `json:"age"`
   Addr   string `json:"addr"`
   Grade  string
   height int
   Class
}

func (s Student) Study() {
   fmt.Println(s.Name, "Study!")
}

func (s Student) Sleep() {
   fmt.Println(s.Name, "Sleep!")
}
func (s Student) GetAddr() string {
   return s.Addr
}
func (s Student) GetGrade() string {
   return s.Grade
}
func (s Student) DoHomework(hour int) {
   fmt.Printf("%s DoHomework %d hour\n", s.Name, hour)
}

type Student2 struct {
   Name   string `json:"name"`
   Age    int    `json:"age"`
   Addr   string `json:"addr"`
   Grade  string
   height int
   Class
}

func (s *Student2) Study() {
   fmt.Println(s.Name, "Study!")
}

func (s *Student2) Sleep() {
   fmt.Println(s.Name, "Sleep!")
}
func (s *Student2) GetAddr() string {
   return s.Addr
}
func (s *Student2) GetGrade() string {
   return s.Grade
}
func (s *Student2) DoHomework(hour int) {
   fmt.Printf("%s DoHomework %d hour\n", s.Name, hour)
}

获取类型信息

使用TypeOf方法获取类型信息。注意TypeKind的区别,Type是指具体类型,Kind是指底层类型。

// 测试各种类型
func Test1(t *testing.T) {
   var f0 float64 = 3.14
   typeOfF0 := reflect.TypeOf(f0)
   fmt.Println("Name:", typeOfF0.Name())
   fmt.Println("Kind:", typeOfF0.Kind())

   var f PFloat64 = 3.15
   t1 := reflect.TypeOf(f)
   fmt.Println("Name:", t1.Name())
   fmt.Println("Kind:", t1.Kind())

   cat := Cat{
      Color: "橘色",
   }
   typeOfCat := reflect.TypeOf(cat)
   fmt.Println("Name:", typeOfCat.Name())
   fmt.Println("Kind:", typeOfCat.Kind())
}

Elem方法获取指针类型指向的变量

func Test2(t *testing.T) {
   cat := Cat{
      Color: "橘色",
   }
   typeOfCatPtr := reflect.TypeOf(&cat)
   fmt.Println("Name:", typeOfCatPtr.Name(), "len:", len(typeOfCatPtr.Name()))
   fmt.Println("Kind:", typeOfCatPtr.Kind())
   elem := typeOfCatPtr.Elem() // 取指针指向的元素,相当于用*号解引用
   fmt.Println("Name:", elem.Name())
   fmt.Println("Kind:", elem.Kind())
   fmt.Println("Elem:", elem.Elem()) // panic: reflect: Elem of invalid type main.Cat
}

使用ValueOf方法获取值;使用SetXXX方法修改值。

// 测试通过反射修改变量值
func Test5(t *testing.T) {
   var x float64 = 3.14
   valueOfX := reflect.ValueOf(x)
   // 获取值
   fmt.Println("valueOfX.value:", valueOfX.Interface().(float64))
   fmt.Println("CanSet:", valueOfX.CanSet())
   //valueOfX.SetFloat(6.66) // panic: reflect: reflect.Value.SetFloat using unaddressable value。因为valueOfX是x变量副本的值,并不能改变原本的x变量值,类似函数想修改函数外的变量值时必须传变量的指针

   valueOfXPtr := reflect.ValueOf(&x)
   fmt.Println("valueOfXPtr.value:", valueOfXPtr.Interface())
   fmt.Println("CanSet:", valueOfXPtr.CanSet())
   //valueOfXPtr.SetFloat(7.77) // panic: reflect: reflect.Value.SetFloat using unaddressable value

   elem := valueOfXPtr.Elem()
   fmt.Println("CanSet:", elem.CanSet())
   elem.SetFloat(8.88)
   fmt.Println("x:", x)
   fmt.Println("elem.Value:", elem.Interface().(float64))
   elem.Set(reflect.ValueOf(9.99))
   fmt.Println("x:", x)
   fmt.Println("elem.Value:", elem.Interface().(float64))
}

访问结构体字段

获取结构体字段信息

// 测试获取结构体的字段
func Test3(t *testing.T) {
   stu := Student{}
   typeOfStu := reflect.TypeOf(stu)
   fmt.Println("NumField:", typeOfStu.NumField())
   for i := 0; i < typeOfStu.NumField(); i++ {
      field := typeOfStu.Field(i)
      fmt.Println("Name:", field.Name, ",Index:", field.Index, ",Tag:", field.Tag, ",Offset:", field.Offset, ",PkgPath:", field.PkgPath, ",IsExported:", field.IsExported(), ",Anonymous:", field.Anonymous)
   }

   heightField, ok := typeOfStu.FieldByName("height")
   fmt.Println("heightField.ok:", ok, "heightField.Type:", heightField.Type)
   weightField, ok := typeOfStu.FieldByName("weight")
   fmt.Println("weightField.ok:", ok, "weightField.Type:", weightField.Type)

   field5 := typeOfStu.FieldByIndex([]int{5})
   fmt.Println("Name:", field5.Name)
   field5_0 := typeOfStu.FieldByIndex([]int{5, 0}) // 可以多层访问结构体字段
   fmt.Println("Name:", field5_0.Name, ",Index:", field5_0.Index)
   field5_1 := typeOfStu.FieldByIndex([]int{5, 1})
   fmt.Println("Name:", field5_1.Name, ",Index:", field5_1.Index)

   //typeOfStu.FieldByIndex([]int{6}) // panic: reflect: Field index out of bounds。越界

   nameField, ok := typeOfStu.FieldByName("Name")
   if !ok {
      t.Fatal("fatal: not exist `name` field")
   }
   // 结构体标签
   structTag := nameField.Tag
   jsonTag := structTag.Get("json") // 结构体标签的写法必须严格遵守规则:键和值用冒号分隔,值用双引号括起来,多个键值对之间用空格分隔
   json2Tag := structTag.Get("json2")
   fmt.Println("jsonTag:", jsonTag, ",json2Tag:", json2Tag)

   matchedField, ok := typeOfStu.FieldByNameFunc(func(s string) bool {
      //if s == "Name" || s == "Age" { // 这样不行,必须唯一匹配一个,否则返回没匹配到字段
      if s == "Name" {
         return true
      }
      return false
   })
   fmt.Println("matchedField.ok:", ok, ",matchedField.Name:", matchedField.Name, ",matchedField.Index:", matchedField.Index)
}

修改结构体字段值

// 测试通过反射修改结构体字段
func Test6(t *testing.T) {
   stu := Student{
      Name: "PENG",
      Age:  16,
   }
   var learn Learn = stu
   typeOfLearn := reflect.TypeOf(learn) // 返回的是动态类型
   //typeOfLearn.Elem()
   fmt.Println("Name:", typeOfLearn.Name(), ",Kind:", typeOfLearn.Kind())
   elem := reflect.ValueOf(&stu).Elem()
   for i := 0; i < elem.NumField(); i++ {
      field := elem.Type().Field(i)
      fieldValue := elem.Field(i)
      fmt.Println("======================")
      fmt.Println("field.Type == fieldValue.Type():", field.Type == fieldValue.Type(), ",field.Type.Name():", field.Type.Name())
      fmt.Println("field.Name:", field.Name, "field.Index:", field.Index, ",field.Type.Kind:", field.Type.Kind(), ",field.IsExported:", field.IsExported())
      if field.IsExported() {
         fmt.Println("fieldValue.Interface:", fieldValue.Interface())
      }
   }

   fmt.Println("-----修改结构体字段值-----")
   nameField, ok := elem.Type().FieldByName("Name")
   fmt.Println("nameField.Index:", nameField.Index, ",nameField.Type:", nameField.Type, ",ok:", ok)
   nameFieldValue := elem.FieldByName("Name")
   fmt.Println("nameFieldValue.Interface():", nameFieldValue.Interface())
   nameFieldValue.SetString("XZP")
   fmt.Println("stu.Name:", stu.Name)
}

访问类型定义的方法

获取方法信息

// 测试获取类型定义的方法
// 另外,测测包装方法,发现如下现象:方法接收器是Student类型时,*Student类型也有对应的4个方法(证明了包装方法的存在);方法接收器是*Student2类型时,Student2类型却没有对应方法。
func Test4(t *testing.T) {
   stu1 := Student{
      Name: "李六",
      Age:  21,
      Addr: "ddd",
   }
   typeOfStu1 := reflect.TypeOf(stu1)
   fmt.Println("typeOfStu1.Name:", typeOfStu1.Name(), ",typeOfStu1.Kind:", typeOfStu1.Kind(), ",typeOfStu1.NumMethod:", typeOfStu1.NumMethod()) // typeOfStu1.Name: Student ,typeOfStu1.Kind: struct ,typeOfStu1.NumMethod: 4
   for i := 0; i < typeOfStu1.NumMethod(); i++ {
      method := typeOfStu1.Method(i)
      fmt.Printf("typeOfStu1 method[%d].Name:%s\n", method.Index, method.Name)
   }
   fmt.Println("------------------------------------")

   stu1Ptr := &Student{
      Name: "李六指针",
      Age:  210,
      Addr: "ddd ptr",
   }
   typeOfStu1Ptr := reflect.TypeOf(stu1Ptr)
   fmt.Println("typeOfStu1Ptr.Name:", typeOfStu1Ptr.Name(), ",typeOfStu1Ptr.Kind:", typeOfStu1Ptr.Kind(), ",typeOfStu1Ptr.NumMethod:", typeOfStu1Ptr.NumMethod()) // typeOfStu1Ptr.Name:  ,typeOfStu1Ptr.Kind: ptr ,typeOfStu1Ptr.NumMethod: 4
   for i := 0; i < typeOfStu1Ptr.NumMethod(); i++ {
      method := typeOfStu1Ptr.Method(i)
      fmt.Printf("typeOfStu1Ptr method[%d].Name:%s\n", method.Index, method.Name)
   }
   fmt.Println("------------------------------------")

   stu2 := Student2{
      Name: "赵七",
      Age:  22,
      Addr: "eee",
   }
   typeOfStu2 := reflect.TypeOf(stu2)
   fmt.Println("typeOfStu2.Name:", typeOfStu2.Name(), ",typeOfStu2.Kind:", typeOfStu2.Kind(), ",typeOfStu2.NumMethod:", typeOfStu2.NumMethod()) // typeOfStu2.Name: Student2 ,typeOfStu2.Kind: struct ,typeOfStu2.NumMethod: 0
   fmt.Println("------------------------------------")

   stu2Ptr := &Student2{
      Name: "赵七指针",
      Age:  220,
      Addr: "eee ptr",
   }
   typeOfStu2Ptr := reflect.TypeOf(stu2Ptr)
   fmt.Println("typeOfStu2Ptr.Name:", typeOfStu2Ptr.Name(), ",typeOfStu2Ptr.Kind:", typeOfStu2Ptr.Kind(), ",typeOfStu2Ptr.NumMethod:", typeOfStu2Ptr.NumMethod()) // typeOfStu2Ptr.Name:  ,typeOfStu2Ptr.Kind: ptr ,typeOfStu2Ptr.NumMethod: 4
   for i := 0; i < typeOfStu2Ptr.NumMethod(); i++ {
      method := typeOfStu2Ptr.Method(i)
      fmt.Printf("typeOfStu2Ptr method[%d].Name:%s\n", method.Index, method.Name)
   }
}

调用方法

// 测试反射调用方法
func Test44(t *testing.T) {
   stu := Student{
      Name: "张三",
      Age:  18,
      Addr: "aaa",
      Class: Class{
         Num:    0,
         Amount: 30,
      },
   }
   valueOfStu := reflect.ValueOf(stu)
   // 无参无返回值
   methodSleep := valueOfStu.MethodByName("Sleep")
   result := methodSleep.Call([]reflect.Value{})
   fmt.Println("len(methodSleep.result):", len(result))

   // 无参有返回值
   methodGetAddr := valueOfStu.MethodByName("GetAddr")
   result = methodGetAddr.Call([]reflect.Value{})
   fmt.Printf("len(methodGetAddr.result):%d, result:%s\n", len(result), result[0].String())

   // 有参无返回值
   methodDoHomework := valueOfStu.MethodByName("DoHomework")
   result = methodDoHomework.Call([]reflect.Value{reflect.ValueOf(2)})
   fmt.Printf("len(methodDoHomework.result):%d\n", len(result))
}

判断一个类型是否实现了某接口

使用Implements方法,测试代码如下:

type MyErr string

// 实现error接口
func (m *MyErr) Error() string {
   return string(*m)
}

func Test1(t *testing.T) {
   myErr := MyErr("自定义err")
   typeOfMyErr := reflect.TypeOf(myErr)
   fmt.Printf("typeOfMyErr.Name:%s, typeOfMyErr.Kind:%v\n", typeOfMyErr.Name(), typeOfMyErr.Kind())

   typeOfErrorPtr := reflect.TypeOf((*error)(nil))
   fmt.Printf("typeOfErrorPtr.Name:%s, typeOfErrorPtr.Kind:%v\n", typeOfErrorPtr.Name(), typeOfErrorPtr.Kind())

   // 获取error接口的反射类型
   typeOfErrorPtrElem := typeOfErrorPtr.Elem()
   fmt.Printf("typeOfErrorPtrElem.Name:%s, typeOfErrorPtrElem.Kind:%v\n", typeOfErrorPtrElem.Name(), typeOfErrorPtrElem.Kind())

   ok := typeOfMyErr.Implements(typeOfErrorPtrElem)
   fmt.Println("ok?", ok)

   ok = reflect.TypeOf(&myErr).Implements(typeOfErrorPtrElem)
   fmt.Println("ok?", ok)
}

执行结果:

=== RUN   Test1
typeOfMyErr.Name:MyErr, typeOfMyErr.Kind:string
typeOfErrorPtr.Name:, typeOfErrorPtr.Kind:ptr
typeOfErrorPtrElem.Name:error, typeOfErrorPtrElem.Kind:interface
ok? false
ok? true
--- PASS: Test1 (0.00s)
PASS

重点:
在go语言中,想要获取接口类型reflect.Type对象,只能这样获取:reflect.TypeOf((*SomeInterface)(nil)).Elem()