反射(Reflect)的介绍以及用法

反射的前言

有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享 同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我 们就可以用到反射。
空接口可以存储任意类型的变量,那我们如何知道这个空接口保存数据的类型是什么? 值是什么呢?

  • 可以使用类型断言
  • 可以使用反射实现,也就是在程序运行时动态的获取一个变量的类型信息和值信息。
  • 把结构体序列化成 json 字符串,自定义结构体 Tag 标签的时候就用到了反射
  • ORM 框架就用到了反射技术,ORM:对象关系映射(Object Relational Mapping,简称 ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中

反射的基本介绍

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、 结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们
Golang 中反射可以实现以下功能:
1.反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
2. 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag
3.通过反射,可以修改变量的值,可以调用关联的方法
Go 语言中的变量是分为两部分的:
• 类型信息:预先定义好的元信息。
• 值信息:程序运行过程中可动态变化的。

在 GoLang 的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。

在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两 部 分 组 成 , 并 且 reflect 包 提 供 了 reflect.TypeOf 和reflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type。

reflect.TypeOf()获取任意值的类型对象

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中, 当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)

import (
	"fmt"
	"reflect"
	"testing"
)
type myInt int
type Address struct {
	Name   string
	mobile int
}

func reflectType(a interface{}) {
	var info = reflect.TypeOf(a)
	//fmt.Println(info)
	fmt.Printf("类型:%v 类型名称:%v 类型种类:%v \n", info, info.Name(), info.Kind())
}
//反射获取任意变量的类型
func TestReflectType(t *testing.T) {
	var a myInt = 10
	var b = 20
	var c = "hello"
	var d = true
	var ad = Address{
		Name:   "lili",
		mobile: 15878456321,
	}
	var e = [3]int{1, 2, 3}
	var f = make([]int, 3, 3)
	f[0] = 1
	f[1] = 2
	f[2] = 3
	var m = make(map[int]string)
	m[1] = "hello"
	m[2] = "world"
	reflectType(a)  //类型:demo.myInt 类型名称:myInt 类型种类:int
	reflectType(b)  //类型:int 类型名称:int 类型种类:int
	reflectType(c)  //类型:string 类型名称:string 类型种类:string
	reflectType(d)  //类型:bool 类型名称:bool 类型种类:bool
	reflectType(ad) //类型:demo.Address 类型名称:Address 类型种类:struct
	reflectType(e)  //类型:[3]int 类型名称: 类型种类:array
	reflectType(f)  //类型:[]int 类型名称: 类型种类:slice
	reflectType(m)  //类型:map[int]string 类型名称: 类型种类:map

}

在reflect 包中定义的 Kind 类型如下

type Kind uint

const (
	Invalid Kind = iota //非法类型
	Bool                //布尔型
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64          // 64位复数类型
	Complex128		   // 128位复数类型
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer      //底层指针
)

reflect.ValueOf()

reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换

方法说明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均 可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
//反射获取任意变量的值
func reflectValue(a interface{}) {
	var v = reflect.ValueOf(a)
	var kind = v.Kind()
	switch kind {
	case reflect.Int64:
		fmt.Printf("匹配的类型为Int64:%v\n",v.Int())
	case reflect.Float32:
		fmt.Printf("匹配的类型为Float32:%v\n",v.Float())
	case reflect.Float64:
		fmt.Printf("匹配的类型为Float64:%v\n",v.Float())
	case reflect.String:
		fmt.Printf("匹配的类型为String:%v\n",v.String())
	default:
		fmt.Println("没有匹配的类型")

	}
}
func TestReflectValue(t *testing.T) {
	var a int64 = 10
	var b = 2.10
	var c = "hello"
	reflectValue(a) //匹配的类型为Int64:10
	reflectValue(b) //匹配的类型为Float64:2.1
	reflectValue(c) //匹配的类型为String:hello
}

通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地 址才能修改变量值。而反射中使用专有的 Elem()方法来获取指针对应的值

方法
func (v Value) SetUint(x uint64)
func (v Value) SetString(x string)
func (v Value) SetBool(x bool)
func (v Value) SetInt(x int64)
func (v Value) SetFloat(x float64)
func (v Value) SetComplex(x complex128)
func (v Value) SetBytes(x []byte)
func (v Value) SetPointerx unsafe.Pointer)
func (v Value) SetCap(n int)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) Set(x Value)
//反射获取任意变量的值,并设置
func reflectSetValue(a interface{})  {
	v := reflect.ValueOf(a)
	//fmt.Println(v.Kind())//ptr
	if v.Elem().Kind()== reflect.Int64{
		v.Elem().SetInt(1290)
	}else if v.Elem().Kind() == reflect.String{
		v.Elem().SetString("你好呀")
	}
}
func TestReflectSetValue(t *testing.T) {
	var a int64 = 100
	reflectSetValue(&a)
	var b string = "hello"
	reflectSetValue(&b)

	fmt.Println(a) //1290
	fmt.Println(b) //你好呀

}

结构体反射

任意值通过 reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息
reflect.Type 中与获取结构体成员相关的的方法如下表所示

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息
NumField() int返回结构体成员字段数量
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息
FieldByNameFunc(match func(string) bool) (StructField,bool)根据传入的匹配函数匹配需要的字段
NumMethod() int返回该类型的方法集中方法的数目
Method(int) Method返回该类型方法集中的第 i 个方法(i和结构体的方法顺序没有关系,和结构体方法的ASCII有关系)
MethodByName(string)(Method, bool)根据方法名返回该类型方法集中的方法

StructField 类型用来描述结构体中的一个字段的信息。StructField 的定义

type StructField struct {
	Name	 	string		// Name 是字段的名字
	PkgPath  	string		// PkgPath 是非导出字段的包路径,对导出字段该字段为""
	Type     	type        // 字段的类型
	Tag      	StructTag   // 字段的标签
	Offset   	unintptr    // 字段在结构体中的字节偏移量
	Index    	[]int		// 用于Type.FieldByIndex 时的索引切片
	Anonymous	bool        // 是否匿名字段
}

获取结构体属性,获取执行结构体方法

type UserInfo struct {
	Name    string
	Age     int
	Address string
}

func (u *UserInfo) SetUserInfo(name string, age int, address string) {
	u.Name = name
	u.Address = address
	u.Age = age
}
func (u UserInfo) GetUserInfo() {
	fmt.Printf("姓名:%v 年龄:%v 住址:%v", u.Name, u.Age, u.Address)
	return
}
func (u UserInfo) PrintInfo() {
	fmt.Println("测试方法执行")
}
func PrintStruct(u interface{}) {
	var t = reflect.TypeOf(u)
	var v = reflect.ValueOf(u)
	if t.Kind() != reflect.Struct && v.Elem().Kind() != reflect.Struct {
		fmt.Println("请输入结构体类型")
		return
	}
	//通过Field获取结构体的字段
	var filed0 = t.Field(0) //获取第1个
	fmt.Printf("%#v",filed0)//reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x522500), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
	fmt.Printf("字段名称%v",filed0.Name)//字段名称Name
	fmt.Printf("字段类型%v",filed0.Type)//字段类型string
	fmt.Printf("字段标签%v",filed0.Tag)//字段标签
	//通过FieldByName获取结构体的字段
	var filed1,_ = t.FieldByName("Age")
	fmt.Printf("字段名称%v",filed1.Name)//字段名称Age
	fmt.Printf("字段类型%v",filed1.Type)//字段类型int
	fmt.Printf("字段标签%v",filed1.Tag)//字段标签

	//通过Method方法获取结构体的方法
	var fun = t.Method(0) //和结构体的方法顺序没有关系,和结构体方法的ASCII有关系
	fmt.Println(fun)//{PrintInfo  func(demo.UserInfo) <func(demo.UserInfo) Value> 0}
	fmt.Println(fun.Name) //PrintInfo
	fmt.Println(fun.Type) //func(demo.UserInfo)
	//通过MethodByName方法获取结构体的方法
	var fun1,_ = t.MethodByName("PrintInfo")
	fmt.Println(fun1.Name) //PrintInfo
	fmt.Println(fun1.Type) //func(demo.UserInfo)
	//执行PrintInfo方法 通过v.Method(0)获取
	v.Method(0).Call(nil)//执行PrintInfo方法
	//执行PrintInfo方法 通过v.MethodByName获取
	v.MethodByName("PrintInfo").Call(nil)//执行PrintInfo方法
	//v.MethodByName("GetUserInfo").Call(nil)//姓名:lishi 年龄:20 住址:浙江省拱树区
	//执行方法传参
	var arr []reflect.Value
	arr = append(arr,reflect.ValueOf("张一山"))
	arr = append(arr,reflect.ValueOf(12))
	arr = append(arr,reflect.ValueOf("浦东新区世博园"))
	v.MethodByName("SetUserInfo").Call(arr)
}
func TestPrint(t *testing.T) {
	var u = UserInfo{
		Name:    "lishi",
		Age:     20,
		Address: "浙江省拱树区",
	}
	fmt.Println(u)//{lishi 20 浙江省拱树区}
	PrintStruct(&u)
	fmt.Println(u)//{张一山 12 浦东新区世博园}
}

注意

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用, 原因有以下两个

  • 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发 panic, 那很可能是在代码写完的很长时间之后
  • 大量使用反射的代码通常难以理解