Go-Reflect学习笔记

63 阅读3分钟

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

前言

Go是一个提供很好的反射特性的静态语言,有很多框架都依赖于反射特性已简化代码,提升开发效率。

大部分的反射方法都在reflect包中,它提供了运行时反射能力,在包中有两个重要的类型reflect.typereflect.value,分别对应类型的相关信息和类型的值。

  • reflect.TypeOf方法来获得reflect.type类型
  • reflect.ValueOf方法来获得reflect.value类型

反射类型之间转换关系大致如下图

image.png

reflect.Type类型

正如上面所说,通常都是直接使用reflect.TypeOf方法获得类型的信息的。

func testReflect() {
    var i int = 1
    var s string = "Hello"
    var student Student
    intType := reflect.TypeOf(i)
    strType := reflect.TypeOf(s)
    studentType := reflect.TypeOf(student)
    fmt.Println(intType, strType, studentType)
    // int string main.Student
}

获得结构体属性

通常都需要动态获得结构体的相关属性和相关标签信息,以便实现某些需求。

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
​
func testStructReflect() {
    studentType := reflect.TypeOf(Student{})
    numField := studentType.NumField()
    for i := 0; i < numField; i++ {
        field := studentType.Field(i)
        fmt.Printf("fieldName:%s\tfieldType:%v\ttagValue:%s\n", field.Name, field.Type, field.Tag.Get("json"))
    }
}
// fieldName:Name  fieldType:string        tagValue:name
// fieldName:Age   fieldType:int   tagValue:age

除了通过简单的遍历,reflect包还提供了使用byNameByIndex来获得字段信息

name, ok := studentType.FieldByName("Name")
fmt.Println(name, ok) // {Name  string json:"name" 0 [0] false} true 
age := studentType.FieldByIndex([]int{1})
fmt.Println(age) // {Age  int json:"age" 16 [1] false}   

获得结构体方法

获得结构体方法,注意这里是reflect.TypeOf(&Student{}),这是因为实体和指针方法集的问题啦,如果不明白为啥就需要去学习下啦。

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
​
func (s *Student) Say() {
    fmt.Println(s.Name, s.Age)
}
​
func (s *Student) SetName(name string) {
    s.Name = name
}
​
func testStructMethodReflect() {
    studentType := reflect.TypeOf(&Student{})
    numMethod := studentType.NumMethod()
    for i := 0; i < numMethod; i++ {
        method := studentType.Method(i)
        methodType := method.Func.Type() // 获得方法本身的Type
        fmt.Printf("methodName:%s\targsNum:%d\treturnNum:%d\n", method.Name, methodType.NumIn(), methodType.NumOut())
    }
}
// methodName:Say  argsNum:1       returnNum:0
// methodName:SetName      argsNum:2       returnNum:0

指针类型转换值类型

有时因为上面提到的方法集的问题,有时需要将指针类型转换为值类型。

func testConvert() {
    studentPointType := reflect.TypeOf(&Student{})
    elem := studentPointType.Elem() // 获得所指向的值类型
    fmt.Println(elem) // main.Student
}

判断是否实现某个接口

// 定义一个接口type Sayer interface {
    Say()
}
​
​
func testStructImplement() {
    sayer := (*Sayer)(nil) // 获得接口指针类型
    studentType := reflect.TypeOf(&Student{})
    fmt.Println(studentType.Implements(reflect.TypeOf(sayer).Elem())) // true
}

reflect.Value类型

同样,如果我们要获得变量的值,就通过reflect.ValueOf方法来得到。

func testValueOf() {
    var i int = 1
    var str string = "Hello"
    intValue := reflect.ValueOf(i)
    strValue := reflect.ValueOf(str)
    fmt.Println(intValue, strValue)
    // 1 Hello
}

更新变量值

当我们想要更新变量值时,就通过reflect.Value.Set方法来设置。需要注意的是由于Go的函数调用都是值传递,因此经过interface{}的隐形转换,操作的对象跟一开始没有啥关系了,并不能修改到原始变量的值。

    var i int = 1
    intValue := reflect.ValueOf(i)
    intValue.SetInt(2)
    // reflect: reflect.Value.SetInt using unaddressable value 

运行上面的代码,会导致程序崩溃,因此我们需要采用指针的方式来实现。

func testValueOf() {
    var i int = 1
    var str string = "Hello"
    intValue := reflect.ValueOf(&i)
    strValue := reflect.ValueOf(&str)
    fmt.Println(intValue.Elem(), strValue.Elem()) // 1 Hello
    intValue.Elem().SetInt(2)
    strValue.Elem().SetString("World")
    fmt.Println(intValue.Elem(), strValue.Elem())// 2 World
}

当调用相关Set方法时,方法内部会检测当前的字段是否可以被设置以及是否公开

func (v Value) Set(x Value) {
    v.mustBeAssignable() // 检测是否可以设置
    x.mustBeExported() // 检测是否公开
    // ...
}

指针和值互相转换

func testValueConvert() {
    student := Student{}
    pointValue := reflect.ValueOf(&student)
    fmt.Println(pointValue.Elem()) // 值类型
    fmt.Println(pointValue.Elem().Addr()) // 指针类型
}
​

调用结构体方法

func testCall() {
   studentValue := reflect.ValueOf(&Student{})
   studentValue.Elem().FieldByName("Name").SetString("Tom")
   studentValue.Elem().FieldByName("Age").SetInt(18)
   method := studentValue.MethodByName("Say")
   values := method.Call([]reflect.Value{}) // Tom 18
}

小小总结

本章介绍了reflect.Type和reflect.Value这两个重要的反射类型,以及平常开发中常用的相关API,Reflect包为我们提供了很多强大的功能,当过度使用反射也一样会降低程序性能。

参考资料