跳到主要内容

反射

前言

反射主要是指程序可以访问、检测和修改它本身的状态或行为的一种能力,是一种动态获取变量类型信息和值信息的一种机制。一些框架采用反射机制,能让框架更加通用和开放。Go语言的反射机制实现了在运行时动态调用对象的方法和属性的功能,其相关包为标准库的reflect

Go语言的反射主要通过Type和Value两个基本概念来表达。其中Type主要用于表示被反射变量的类型信息,而Value用于表示被反射变量自身的实例信息。

反射三原则:

  • 反射可以通过接口变量值获取反射对象
  • 反射可以通过反射对象获取接口变量值
  • 修改反射对象的前提是值必须是可设置的

反射类型

  1. 示例1,基础
package main

import (
"fmt"
"reflect"
)

// 定义一个Person接口
type Person interface {
SayHello(name string)
Run() string
}

// 定义一个Hero结构体实现Person接口中的方法
type Hero struct {
Name string
Age int
Speed int
}

func (hero *Hero) SayHello(name string) {
fmt.Printf("Hello, %s. I am %s.\n", name, hero.Name)
}

func (hero *Hero) Run() string {
fmt.Printf("I am running at speed %d\n", hero.Speed)
return "Running"
}

func main() {
typeOfHero := reflect.TypeOf(Hero{})
// Hero's type is main.Hero, kind is struct
fmt.Printf("Hero's type is %s, kind is %s\n",typeOfHero, typeOfHero.Kind())
}

在Go语言中,存在着Type和Kind的区别。如上述代码所示,Hero的Type为main.Hero,kind为struct。Type是指变量所属的类型,包括系统原生的数据类型和通过type自定义的类型。Kind是指变量类型所归属的品种。reflect.Kind的定义可参考 https://pkg.go.dev/reflect#Kind

const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
  1. 类型对象reflect.StructFieldreflect.Method。如果变量是一个结构体,我们还可以通过结构体域类型对象reflect.StructField来获取结构体下字段的类型属性。
// 获取一个结构体内的字段数量
NumField() int
// 根据 index 获取结构体内的成员字段类型对象
Field(i int) StructField
// 根据字段名获取结构体内的成员字段类型对象
FieldByName(name string) (StructFiledm bool)

其中reflect.StructField包括以下属性

type StructField struct {
Name string
Type Type
Tag StructTag
Offset uintptr // 字节偏移
Index []int // 成员字段的index
Anonymous bool // 成员字段是否公开
}

示例

package main

import (
"fmt"
"reflect"
)

type Person interface {
SayHello(name string)
Run() string
}

type Hero struct {
Name string
Age int
Speed int
}

func (hero *Hero) SayHello(name string) {
fmt.Printf("Hello, %s. I am %s.\n", name, hero.Name)
}

func (hero *Hero) Run() string {
fmt.Printf("I am running at speed %d\n", hero.Speed)
return "Running"
}

func main() {
typeOfHero := reflect.TypeOf(Hero{})
// Hero's type is main.Hero, kind is struct
// fmt.Printf("Hero's type is %s, kind is %s\n", typeOfHero, typeOfHero.Kind())

for i := 0; i < typeOfHero.NumField(); i++ {
fmt.Printf("field name is %s, type is %s, kind is %s\n",
typeOfHero.Field(i).Name,
typeOfHero.Field(i).Type,
typeOfHero.Field(i).Type.Kind())
}

nameFiled,_ := typeOfHero.FieldByName("Name")
fmt.Printf("filed name is %s, type is %s, kind is %s\n", nameFiled.Name, nameFiled.Type, nameFiled.Type.Kind())
}
  1. 反射接口,获取接口中的方法
package main

import (
"fmt"
"reflect"
)

type Person interface {
SayHello(name string)
Run() string
}

type Hero struct {
Name string
Age int
Speed int
}

func (hero *Hero) SayHello(name string) {
fmt.Printf("Hello, %s. I am %s.\n", name, hero.Name)
}

func (hero *Hero) Run() string {
fmt.Printf("I am running at speed %d\n", hero.Speed)
return "Running"
}

func main() {
// 声明一个Person接口, 并用Hero作为接收器
var person Person = &Hero{}
typeOfPerson := reflect.TypeOf(person)

for i := 0; i < typeOfPerson.NumMethod(); i++ {
fmt.Printf("method is %s, type is %s, kind is %s\n", typeOfPerson.Method(i).Name,
typeOfPerson.Method(i).Type,
typeOfPerson.Method(i).Type.Kind())
}

// 获取指定方法
method, _ := typeOfPerson.MethodByName("Run")
fmt.Printf("method is %s, type is %s, kind is %s\n", method.Name, method.Type, method.Type.Kind())
}

反射值对象

nil

综合示例

  • simple
package main
import (
"fmt"
"reflect"
"time"
)

func main() {
num := 3.14
fmt.Println(reflect.TypeOf(num)) // float64
fmt.Println(reflect.TypeOf(num).Kind()) // float64
fmt.Println(reflect.ValueOf(num)) // 3.14

t1 := time.Now()
fmt.Println(reflect.TypeOf(t1)) // time.Time
fmt.Println(reflect.ValueOf(t1))

var if1 interface{}
if1 = "hello world"
fmt.Println(reflect.TypeOf(if1)) // string
fmt.Println(reflect.ValueOf(if1)) // hello world
}
  • 获取结构体字段和方法
package main
import (
"fmt"
"reflect"
// "time"
)

type Student struct {
id string
Name string
Age int
}

func (this *Student) GetName() {
fmt.Println(this.Name)
}

func (this *Student) GetAge() {
fmt.Println(this.Age)
}

func (this *Student) getId() {
fmt.Println(this.id)
}

// 打印参数类型中的字段信息和方法信息
func printFiledMethod(o interface{}) {
valueOf := reflect.ValueOf(o)
typeOf := valueOf.Type()
fmt.Println(typeOf)

// 判断参数的类型是否为结构体,防止NumField报错
if typeOf.Kind() == reflect.Struct {
// NumField() 用于获取结构体的字段数量
for i:=0;i<typeOf.NumField();i++ {
field := typeOf.Field(i)
// 判断是否为私有字段
if valueOf.Field(i).CanInterface() {
value := valueOf.Field(i).Interface()
fmt.Printf("%s:%v = %v\n",field.Name, field.Type, value)
} else {
fmt.Printf("%s:%v = %v\n",field.Name, field.Type, "私有字段")
}
}
}

// 获取非私有方法
for i:=0;i<typeOf.NumMethod();i++ {
m := typeOf.Method(i)
fmt.Printf("%s:%v\n",m.Name, m.Type)
}
}

func main() {
stu := Student{Name: "Jack", Age: 24}
fmt.Println("==================")
printFiledMethod(stu)
fmt.Println("==================")
printFiledMethod(&stu)
fmt.Println("==================")
a := 2
printFiledMethod(a)
fmt.Println("==================")
}

参考文章

  • 汪明 - 《Go并发编程实战》清华大学出版社