反射的前言
有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享 同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我 们就可以用到反射。
空接口可以存储任意类型的变量,那我们如何知道这个空接口保存数据的类型是什么? 值是什么呢?
- 可以使用类型断言
- 可以使用反射实现,也就是在程序运行时动态的获取一个变量的类型信息和值信息。
- 把结构体序列化成 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, 那很可能是在代码写完的很长时间之后
- 大量使用反射的代码通常难以理解