什么情况下用到反射
有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。
- 空接口可以存储任意类型的变量,那如何知道这个空接口保存数据的类型是什么?值是什么?
- 可以使用类型断言
- 可以使用反射实现,也就是在程序运行时动态地获取一个变量的类型信息和值信息。
- 把结构体序列化成json字符串,自定义结构体Tab标签的时候就用到了反射
反射的基本介绍
反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go可以实现的功能
- 反射可以在程序运行期间动态地获取变量的各种信息,比如变量的类型、类别
- 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、方法
- 通过反射,可以修改变量的值,可以调用关联的方法。
- Go语言的变量分为两个部分
- 类型信息:预先定义好的元信息
- 值信息:程序运行过程中可动态变化的
在Go语言中,反射的相关功能由内置的
reflect
包提供,任意接口值在反射中都可以理解为由reflect.Type
和reflect.Value
两部分组成,并且reflect
包提供了reflect.TypeOf
和reflect.ValueOf
两个重要的函数来获取任意对象的Type和Value
- reflect.TypeOf()获取任意值的类型对象
type myInt int
type Person struct {
Name string
Age int
}
func reflectFn(x interface{}) {
v := reflect.TypeOf(x)
fmt.Println(v)
}
func main() {
a := 10
b := 23.4
c := "hello"
d := true
reflectFn(a)
reflectFn(b)
reflectFn(c)
reflectFn(d)
var e myInt = 34
var f = Person{
Name: "张三",
Age: 20,
}
var g = 20
reflectFn(e)
reflectFn(f)
reflectFn(&g)
//int
//float64
//string
//bool
//main.myInt
//main.Person
//*int
}
- type Name和type Kind
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类Kind。
type myInt int
type Person struct {
Name string
Age int
}
func reflectFn(x interface{}) {
v := reflect.TypeOf(x)
//v.Name()//类型名称
//v.Kind()//种类
fmt.Printf("类型:%v 类型名称:%v 类型种类:%v", v, v.Name(), v.Kind())
fmt.Println(v)
}
func main() {
a := 10
b := 23.4
c := "hello"
d := true
reflectFn(a)
reflectFn(b)
reflectFn(c)
reflectFn(d)
var e myInt = 34
var f = Person{
Name: "张三",
Age: 20,
}
var g = 20
var h = [3]int{1, 3, 5}
var i = []int{11, 22, 33}
reflectFn(e)
reflectFn(f)
reflectFn(&g)
reflectFn(h)
reflectFn(i)
}
//类型:int 类型名称:int 类型种类:intint
//类型:float64 类型名称:float64 类型种类:float64float64
//类型:string 类型名称:string 类型种类:stringstring
//类型:bool 类型名称:bool 类型种类:boolbool
//类型:main.myInt 类型名称:myInt 类型种类:intmain.myInt
//类型:main.Person 类型名称:Person 类型种类:structmain.Person
//类型:*int 类型名称: 类型种类:ptr*int
//类型:[3]int 类型名称: 类型种类:array[3]int
//类型:[]int 类型名称: 类型种类:slice[]int
reflect.ValueOf()
- 空接口类型和整型计算(使用类型断言)
type Person struct {
Name string
Age int
}
func reflectValue(x interface{}) {
b, _ := x.(int)
var num = 10 + b
fmt.Println(num)
}
func main() {
var a = 13
reflectValue(a)
}
reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值 的信息。reflect.Value与原始值之间可以互相转换。
type myInt int
type Person struct {
Name string
Age int
}
func reflectValue(x interface{}) {
//b, _ := x.(int)
//var num = 10 + b
//fmt.Println(num)
v := reflect.ValueOf(x)
//var n = v + 12//mismatched types reflect.Value and int
fmt.Println(v)//13
//反射获取变量的原始值
var m = v.Int() + 12
fmt.Println(m) //25
}
func main() {
var a = 13
reflectValue(a)
}
reflect.Value类型提供的获取原始值的方法如下:
方法 | 说明 |
---|---|
Interface interface | 将值以interface{}类型返回 |
Int() int64 | 将值以int类型返回 |
Uint() unit64 | 将值以uint类型返回 |
Float() float64 | 将值以双精度(float)类型返回 |
Bool() bool | 将值以bool类型返回 |
Bytes() []bytes | 将值以自己数组[]bytes类型返回 |
String() string | 将值以字符串类型返回 |
…… | …… |
- demo
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
kind := v.Kind()
switch kind {
case reflect.Int:
fmt.Println("int类型的原始值+10", v.Int()+10)
case reflect.Float32:
fmt.Println("Float32类型的原始值+10", v.Float()+10)
case reflect.Float64:
fmt.Println("Float64类型的原始值+10", v.Float()+10)
case reflect.String:
fmt.Println("String类型的原始值", v.String())
default:
fmt.Println("暂未判断该类型")
}
}
func main() {
var a int64 = 100
var b float32 = 3.14
var c string = "你好golang"
reflectValue(a)
reflectValue(b)
reflectValue(c)
}
//暂未判断该类型
//Float32类型的原始值+10 13.140000104904175
//String类型的原始值 你好golang
- 通过反射改变原始值
v.Elem().Kind()获取原始类型,传入的值需要是指针
func reflectSetValue1(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(120)
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
fmt.Println(v.Kind(), v.Elem().Kind())
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(123)
} else if v.Elem().Kind() == reflect.String{
v.Elem().SetString("你好golang")
}
}
func main() {
var a int64 = 100
//reflectSetValue1(a)
// reflect.Value.SetInt using unaddressable value
var b string = "Hello world"
reflectSetValue2(&a)
reflectSetValue2(&b)
fmt.Println(a,b)
}
//ptr int64
//ptr string
//123 你好golang
结构体反射
- StructField类型
StructField类型用来描述结构体中的一个字段的信息。
type StructField struct {
Name string //字段名称
PkgPath string //非导出字段的包路径,对导出字段该字段为""
Type Type //字段的类型
Tag StructTag //字段的标签
Offset uintptr //字段在结构体中的字节偏移量
Infex []int //用于Type.FieldByIndex时的索引切片
Anonymous bool //是否匿名字段
}
- 判断参数是否为结构体类型
type Student struct {
Name string `json:"name" form:"username"'`
Age int `json:"age"`
Score int `json:"score"`
}
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
//判断参数是否为结构体类型
if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
fmt.Println("传入的参数不是结构体")
return
}
}
func main() {
stu1 := Student{
Name: "小明",
Age: 18,
Score: 98,
}
PrintStructField(stu1)
}
- 通过类型变量里的Field获取结构体的字段
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
field0 := t.Field(0)
fmt.Println("字段名称", field0.Name)
fmt.Println("字段类型", field0.Type)
fmt.Println("tag标签", field0.Tag.Get("json"))
fmt.Println("字段Tag", field0.Tag.Get("form"))
fmt.Println("--------------")
}
//字段名称 Name
//字段类型 string
//tag标签 name
//字段Tag username
//--------------
- 通过类型变量里的FieldByName可以获取结构体的字段
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
//v := reflect.ValueOf(s)
field1, ok := t.FieldByName("Age")
if ok {
fmt.Println("字段名称", field1.Name)
fmt.Println("字段类型", field1.Type)
fmt.Println("tag标签", field1.Tag.Get("json"))
}
fmt.Println("--------------")
}
//字段名称 Age
//字段类型 int
//tag标签 age
//--------------
- 通过类型变量里的NumField获取到该结构体有几个字段
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
var fieldCount = t.NumField()
fmt.Println("结构体有", fieldCount, "个属性")
fmt.Println("--------------")
}
//结构体有 3 个属性
//--------------
- 通过值变量获取结构体属性对应的值
func PrintStructField(s interface{}) {
t := reflect.TypeOf(s)
v := reflect.ValueOf(s)
var fieldCount = t.NumField()
fmt.Println(v.FieldByName("Name"))
fmt.Println(v.FieldByName("Age"))
for i := 0; i < fieldCount; i++ {
fmt.Printf("属性名称:%v 属性值:%v 属性类型:%v 属性Tag:%v \n", t.Field(i).Name, v.Field(i), t.Field(i).Type, t.Field(i).Tag.Get("json"))
}
}
//小明
//18
//属性名称:Name 属性值:小明 属性类型:string 属性Tag:name
//属性名称:Age 属性值:18 属性类型:int 属性Tag:age
//属性名称:Score 属性值:98 属性类型:int 属性Tag:score
- 与结构体相关的方法
任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。
reflect.Type中与获取结构体成员相关的方法如下:
方法 | 说明 |
---|---|
Field(i int)StructField | 根据索引,返回索引对应的结构体字段的信息 |
NumField() int | 返回结构体成员字段数量 |
FieldByName(name string)(StructField,bool) | 根据给定字符串返回字符串对应的结构体字段的信息 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息 |
- demo
type Student struct {
Name string `json:"name" form:"username"'`
Age int `json:"age"`
Score int `json:"score"`
}
func (s Student) GetInfo() string {
var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩%v", s.Name, s.Age, s.Score)
return str
}
func (s *Student) SetInfo(name string, age int, score int) {
s.Name = name
s.Age = age
s.Score = score
}
func (s Student) Print() {
fmt.Println("这是一个打印方法")
}
func PrintStructFn(s interface{}) {
//1.判断是否为结构体
t := reflect.TypeOf(s)
//v := reflect.ValueOf(s)
if t.Kind() != reflect.Ptr {
fmt.Println("传入的参数不是一个结构体指针类型")
return
} else if t.Elem().Kind() != reflect.Struct {
fmt.Println("传入的参数不是一个结构体指针类型")
return
}
//2.通过类型变量中的Method获取结构体的方法
method0 := t.Method(0) //和结构体方法的顺序没有关系,与ASCII有关
fmt.Println(method0.Name) //GetInfo
fmt.Println(method0.Type) //func(main.Student) string
fmt.Println("-----------")
//3.通过类型变量获取这个结构体有多少个方法
method1, ok := t.MethodByName("Print")
if ok {
fmt.Println(method1.Name) //Print
fmt.Println(method1.Type) //func(main.Student)
fmt.Println("-----------")
}
v := reflect.ValueOf(s)
//4.通过"值变量"执行方法(注意参数)v.Method(0).Call(nil)或v.MethodByName()
v.Method(1).Call(nil) //这是一个打印方法
info := v.MethodByName("GetInfo").Call(nil)
fmt.Println(info) //[姓名:小明 年龄:18 成绩98]
fmt.Println("------------")
//5.执行方法传入参数(注意需要使用”值变量“,并且要注意参数,接收的参数是[]reflect.Value的切片)
var params []reflect.Value
params = append(params, reflect.ValueOf("小张"))
params = append(params, reflect.ValueOf(19))
params = append(params, reflect.ValueOf(95))
v.MethodByName("SetInfo").Call(params) //执行方法传入参数
newInfo := v.MethodByName("GetInfo").Call(nil)
fmt.Println("通过反射改变属性的值:", newInfo)
//6.获取方法数量
fmt.Println("方法数量", t.NumMethod())//3
}
func main() {
stu1 := Student{
Name: "小明",
Age: 18,
Score: 98,
}
PrintStructFn(&stu1)
}
注意:修改结构体属性时,必须传入结构体的地址,因为结构体是值类型
不要乱用反射
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行时才引发panic(那可能是在代码写完的很长时间之后)
- 大量使用反射的代码通常难以理解
有疑问加站长微信联系(非本文作者)