golang之反射
Posted traditional
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang之反射相关的知识,希望对你有一定的参考价值。
什么是反射?
反射:用大白话解释就是,程序在运行期间可以动态地查看某个变量的值的类型、并且还能够动态调用、修改自身的行为。python应该是反射机制最为彪悍的语言了,当然查看自身类型更是不在话下,这一点动态语言显然占据绝对的优势。而golang虽然作为静态语言,但也是支持反射的,主要通过reflect包实现,并且功能还很强大。
为什么要有反射?
有以下两个场景:
当一个函数接收不同类型的参数要表现不同的行为时。
根据外界返回的值的类型不同,表现的行为也不同。
举个栗子:
package main
import "fmt"
func foo(a interface{}){
//这里使用断言,先不使用反射
switch t:=a.(type) {
case int:
fmt.Printf("t是整型,值为%d
", t)
case string:
fmt.Printf("t是字符串,值为%s
", t)
case float64:
fmt.Printf("t是浮点型,值为%f
", t)
default:
fmt.Println("我也不知道什么类型")
}
}
func main() {
var v interface{} = "abc"
foo(v) // t是字符串,值为abc
}
目前我们是通过类型断言来实现的,后面使用反射也是类似的。反射很强大,但是不建议乱用。
反射相关的代码比较难以阅读,代码的可读性也是我们需要考虑的
反射的过程如果出错了,那么不好意思,只会在运行时才会报错,在编译的时候是检测不出来的。这就可能导致,你的服务都已经完美运行一个月了,但是有一天却突然挂了。如果出现这种情况,那么你要好好想想是不是你的反射相关的代码出问题了。
反射是动态的检测变量的值的类型,这个是很损失效率的,如果没必要,那么尽量不要使用反射
reflect.Type和reflect.Value
golang中描述一个变量有两种方式,一个是reflect.Type(通过reflect.TypeOf(xxx)获取)
,另一个则是reflect.Value(通过reflect.ValueOf(xxx)获取)
。Type和Value分别是从一个变量的类型和值来阐述其特征。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
var b = "satori"
var c = 3.14
var d = [3]int{1, 2, 3}
var e = []int{1, 2, 3}
var f = map[int]int{1: 2, 2: 3}
var g = struct {
name string
age int
}{name: "mashiro", age: 17}
var h = new(int)
var i = make(chan int)
fmt.Println(reflect.TypeOf(a), " --- ", reflect.ValueOf(a)) //int --- 123
fmt.Println(reflect.TypeOf(b), " --- ", reflect.ValueOf(b)) //string --- satori
fmt.Println(reflect.TypeOf(c), " --- ", reflect.ValueOf(c)) //float64 --- 3.14
fmt.Println(reflect.TypeOf(d), " --- ", reflect.ValueOf(d)) //[3]int --- [1 2 3]
fmt.Println(reflect.TypeOf(e), " --- ", reflect.ValueOf(e)) //[]int --- [1 2 3]
fmt.Println(reflect.TypeOf(f), " --- ", reflect.ValueOf(f)) //map[int]int --- map[1:2 2:3]
//匿名结构体会直接返回定义的结构体,如果有名字直接返回名字
fmt.Println(reflect.TypeOf(g), " --- ", reflect.ValueOf(g)) //struct { name string; age int } --- {mashiro 17}
fmt.Println(reflect.TypeOf(h), " --- ", reflect.ValueOf(h)) //*int --- 0xc000062080
fmt.Println(reflect.TypeOf(i), " --- ", reflect.ValueOf(i)) //chan int --- 0xc00004c060
}
我们看到对于TypeOf函数来说,你的变量定义的时候是什么类型,那么返回的就是什么类型。只不过此时返回的是一个Type,是一个结构体,你不能直接和字符串去比较,我们可以调用string方法转换成字符串。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
fmt.Println(reflect.TypeOf(a).String()) //int
fmt.Println(reflect.TypeOf(a).String()[0: 2]) //in
}
而对于ValueOf函数来说,返回的就是你的这个变量的值。但是需要注意的是,这个值是一个Value类型,也是一个结构体,想使用的话是需要转化的。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
var b = "satori"
var c = 3.14
var d = [3]int{1, 2, 3}
var e = []int{1, 2, 3}
var f = map[int]int{1: 2, 2: 3}
var g = struct {
name string
age int
}{name: "mashiro", age: 17}
var h = new(int)
var i = make(chan int)
//这里直接调用了Int(),是因为我们知道这里返回的是int。如果你能确定值的类型,那么你可以直接转化
fmt.Println(reflect.ValueOf(a).Int()) //123
//比如这里的b我知道它就是一个string,那么我就直接通过调用String()方法来转了
fmt.Println(reflect.ValueOf(b).String()) //satori
//假设变量c我们不确定类型,那么可以通过如下方法。先转换成interface{},然后采用类型断言的方式就可以了
switch t:= reflect.ValueOf(c).Interface().(type){
case int:
fmt.Println("int", t)
case float64:
fmt.Println("float", t) //float 3.14
}
//而d是一个数组,这种类型,我们没办法直接调用函数转化了。这个时候只能通过interface{}的方式。同理切片、map等等也是如此
fmt.Println(reflect.ValueOf(d).Interface().([3]int)) //[1 2 3]
fmt.Println(reflect.ValueOf(e).Interface().([]int)) //[1 2 3]
fmt.Println(reflect.ValueOf(f).Interface().(map[int]int)) //map[1:2 2:3]
//这里必须写上字段名,而且不能写错,否则也会断言失败。总而言之,你定义的时候写的什么类型,就是什么类型
fmt.Println(reflect.ValueOf(g).Interface().(struct{name string; age int})) //{mashiro 17}
fmt.Println(reflect.ValueOf(h).Interface().(*int)) //0xc000062080
fmt.Println(reflect.ValueOf(i).Interface().(chan int)) //0xc00004c060
}
我们可以看一下,Type在底层的定义。
type Type interface {
//支持很多方法,但是不是每一个都常用
//此类型的变量对齐后所占用的字节数
Align() int
//struct 的字段对齐后占用的字节数
FieldAlign() int
//返回类型方法集里的第 `i` (传入的参数)个方法
Method(int) Method
// 通过名称获取方法
MethodByName(string) (Method, bool)
// 获取类型方法集里导出的方法个数
NumMethod() int
// 类型名称
Name() string
// 返回类型所在的路径,如:encoding/base64
PkgPath() string
// 返回类型的大小,和 unsafe.Sizeof 功能类似
Size() uintptr
//返回类型的字符串表示形式
String() string
//返回类型的类型值
Kind() Kind
// 类型是否实现了接口 u
Implements(u Type) bool
// 是否可以赋值给 u
AssignableTo(u Type) bool
// 是否可以类型转换成 u
ConvertibleTo(u Type) bool
// 类型是否可以比较
Comparable() bool
// 下面这些函数只有特定类型可以调用
// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
// 类型所占据的位数
Bits() int
// 返回通道的方向,只能是 chan 类型调用
ChanDir() ChanDir
// 返回类型是否是可变参数,只能是 func 类型调用
// 比如 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
Elem() Type
// 返回结构体类型的第 i 个字段,只能是结构体类型调用
// 如果 i 超过了总字段数,就会 panic
Field(i int) StructField
// 返回嵌套的结构体的字段
FieldByIndex(index []int) StructField
// 通过字段名称获取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名称符合 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 获取函数类型的第 i 个参数的类型
In(i int) Type
// 返回 map 的 key 类型,只能由类型 map 调用
Key() Type
//返回 Array 的长度,只能由类型 Array 调用
Len() int
//返回类型字段的数量,只能由类型 Struct 调用
NumField() int
//返回函数类型的输入参数个数
NumIn() int
//返回函数类型的返回值个数
NumOut() int
//返回函数类型的第 i 个值的类型
Out(i int) Type
//返回类型结构体的相同部分
common() *rtype
//返回类型结构体的不同部分
uncommon() *uncommonType
}
至于Value,这个结构体里面的成员都是未导出的,关键它绑定了很多的方法,比如我们上面说的:Int(),String(),Interface()等等,也包括Type里面的很多方法。
Type和Kind
另外各位是不是发现虽然我们定义的是什么类型,Type在形式上也是什么类型这种形式虽然保持了一致性,但是不是也有点不好呢?比如说,我们想判断一个函数返回的到底是一个数组还是结构体(假设有一个这样的需求)
,但是我们知道数组个数不同、结构体字段个数不同、类型不同、对了还有名字不同,都会返回不同的结果,难道这些我们在断言的时候都要写一遍吗?数组可能有100个元素,难道光数组我们就要写100个case语句?显然我们不希望这样的结果,我们希望不管数组元素多少个,只要是一个数组,那么返回一个array就好了,如果是一个结构体,那么你返回struct就行。同理指针也是,我不在乎你到底是什么类型的指针,只要是指针,那么你就返回一个pointer,告诉我你是一个指针就行。那么能不能实现呢?显然可以,从变量的类型来描述在golang中除了Type之外还有一个Kind。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
var b = "satori"
var c = 3.14
var d = [3]int{1, 2, 3}
var e = []int{1, 2, 3}
var f = map[int]int{1: 2, 2: 3}
var g = struct {
name string
age int
}{name: "mashiro", age: 17}
var h = new(int)
var i = make(chan int)
//这个Type是一个结构体,调用其Kind方法就能拿到我们想要的结果
fmt.Println(reflect.TypeOf(a), "---", reflect.TypeOf(a).Kind()) //int --- int
fmt.Println(reflect.TypeOf(b), "---", reflect.TypeOf(b).Kind()) //string --- string
fmt.Println(reflect.TypeOf(c), "---", reflect.TypeOf(c).Kind()) //float64 --- float64
fmt.Println(reflect.TypeOf(d), "---", reflect.TypeOf(d).Kind()) //[3]int --- array
fmt.Println(reflect.TypeOf(e), "---", reflect.TypeOf(e).Kind()) //[]int --- slice
fmt.Println(reflect.TypeOf(f), "---", reflect.TypeOf(f).Kind()) //map[int]int --- map
fmt.Println(reflect.TypeOf(g), "---", reflect.TypeOf(g).Kind()) //struct { name string; age int } --- struct
fmt.Println(reflect.TypeOf(h), "---", reflect.TypeOf(h).Kind()) //*int --- ptr
fmt.Println(reflect.TypeOf(i), "---", reflect.TypeOf(i).Kind()) //chan int --- chan
}
我们看到,除了int、string、float之外,其他的都变化了。像数组返回的是array、切片返回的是slice、map返回的是map、结构体直接返回一个struct、指针直接返回一个ptr、channel直接返回一个chan。但是注意:这些返回的是一个Kind类型,我们还是可以调用String方法转成字符串进行比较,当然也可以直接比较。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
var b = "satori"
var c = 3.14
var d = [3]int{1, 2, 3}
var e = []int{1, 2, 3}
var f = map[int]int{1: 2, 2: 3}
var g = struct {
name string
age int
}{name: "mashiro", age: 17}
var h = new(int)
var i = make(chan int)
//调用String()得到字符串
fmt.Println(reflect.TypeOf(a).Kind().String() == "int") //true
//也可以直接比较,使用reflect,里面有很多类型
fmt.Println(reflect.TypeOf(b).Kind() == reflect.String) //true
fmt.Println(reflect.TypeOf(c).Kind() == reflect.Float64) //true
fmt.Println(reflect.TypeOf(d).Kind() == reflect.Array) //true
fmt.Println(reflect.TypeOf(e).Kind() == reflect.Slice) //true
fmt.Println(reflect.TypeOf(f).Kind() == reflect.Map) //true
fmt.Println(reflect.TypeOf(g).Kind() == reflect.Struct) //true
fmt.Println(reflect.TypeOf(h).Kind() == reflect.Ptr) //true
fmt.Println(reflect.TypeOf(i).Kind() == reflect.Chan) //true
}
而且这个Kind()方法除了reflect.Type可以调用之外,reflect.Value也是可以调用的。并且reflect.Value调用Type方法,还能够得到reflect.Type。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
// reflect.TypeOf(a)返回一个Type,但是通过 reflect.ValueOf(a)得到一个Value,然后再手动调用Type()方法得到Type也是可以的
fmt.Println(reflect.TypeOf(a) == reflect.ValueOf(a).Type()) //true
//得到Kind,除了对Type调用Kind()方法之外,还可以通过对Value调用Kind()方法获取。
fmt.Println(reflect.TypeOf(a).Kind() == reflect.ValueOf(a).Kind()) //true
//同理,故意兜个圈子也行
fmt.Println(reflect.TypeOf(a).Kind() == reflect.ValueOf(a).Type().Kind()) //true
}
一张图来表示Type、Value、Kind三者的关系。
通过反射修改原对象
如果通过反射来修改变量,那么反射变量就必须是可设置的。怎么理解呢?我们通过修改反射变量,会影响原变量,就称之为反射变量是可设置的。
package main
import (
"fmt"
"reflect"
)
func main() {
var a = 123
//此时的v就是反射变量,如果这里我们传递了a,而不是a的指针,那么调用Elem()方法是会报错的
//因为golang是值传递,所以获取了反射变量也不会影响原来的变量,因此这里必须要传递指针
//传递了还不够,还必须要调用Elem方法,此时的v才能真正的代表a
v := reflect.ValueOf(&a).Elem()
//调用CanSet,表示该反射变量是否是可设置的
fmt.Println(v.CanSet()) //true
//设置值
v.SetInt(456)
//此时的a已经变了
//总之就如同函数传参一样,如果想要修改原变量,那么反射变量必须获得原变量的地址才行
fmt.Println(a) //456
}
package main
import (
"fmt"
"reflect"
)
type girl struct {
name string
age int `json:"年龄"`
}
func main() {
var g = girl{"mashiro", 17}
//如果是为了拿到值的话,那么使用reflect.ValueOf(&g).Elem()
//这里查看字段属性使用了reflect.TypeOf(&g).Elem()
//另外reflect.TypeOf(&g).Elem()等价于reflect.ValueOf(&g).Type().Elem()
reflect_g := reflect.TypeOf(&g).Elem()
//通过字段名查找相应字段
s, _ := reflect_g.FieldByName("age")
fmt.Println(s.Tag.Get("json")) //年龄
}
反射获取结构体的属性
package main
import (
"fmt"
"reflect"
)
type girl struct {
name string
age int
gender string
}
func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}
func main() {
g := girl{"mashiro", 17, "f"}
//查看有多少个字段,以下几种方式是一样的
t1, t2 := reflect.TypeOf(g), reflect.TypeOf(&g).Elem()
v1, v2 := reflect.ValueOf(g), reflect.ValueOf(&g).Elem()
fmt.Println(t1.NumField(), t2.NumField()) //3 3
fmt.Println(v1.NumField(), v2.NumField()) //3 3
//查看这个结构体绑定了多少个方法,我们看到指针接收者是没有计算在内的
fmt.Println(t1.NumMethod(), t2.NumMethod()) //3 3
fmt.Println(v1.NumMethod(), v2.NumMethod()) //3 3
}
//可能有人搞不清,针对于结构体到底什么时候用reflect.TypeOf,什么时候用reflect.ValueOf
//我想说的是,对于Type和Value来说,它们有很多方法都是相同的,比如这里的查看字段个数、方法个数。
//当然还有很多名字相同,但是作用不同的方法,比如FieldByName、FieldByIndex等等
//以及调用Field(i)获取第几个字段
//虽然方法名一样,但是Type调用返回的是一个StructField,用于描述结构体对应字段的信息的,比如字段名等等
//而Value调用返回的是一个Value类型,是用来获取结构体对应字段的值的。
反射获取结构体字段的属性
package main
import (
"fmt"
"reflect"
)
type girl struct {
Name string
Age int
Gender string
}
func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}
func main() {
g := girl{"mashiro", 17, "f"}
ref := reflect.TypeOf(g)
for i:=0;i<ref.NumField();i++{
//Field表示获取指定字段,i表示索引
//这里获取的t是一个StructField,包含了字段的信息
t := ref.Field(i)
fmt.Println(t.Name)
/*
Name
Age
Gender
*/
}
val := reflect.ValueOf(g)
for i:=0;i<val.NumField();i++{
//Field作用同上,但是这个是Value调用Field
//返回的不再是StructField,而是一个Value,我们可以直接拿到值
t := val.Field(i)
fmt.Println(t.Interface())
/*
mashiro
17
f
*/
}
}
package main
import (
"fmt"
"reflect"
)
type girl struct {
Name string
Age int
Gender string
}
func (self girl)Foo1(){fmt.Println("Foo1")}
func (self girl)Foo2(){fmt.Println("Foo2")}
func (self *girl)Foo3(){fmt.Println("Foo3")}
func (self girl)Foo4(){fmt.Println("Foo4")}
func main() {
g := girl{"mashiro", 17, "f"}
ref := reflect.TypeOf(g)
//当然也可以针对于方法
for i:=0;i<ref.NumMethod();i++{
t := ref.Method(i)
fmt.Println(t.Name)
/*
Foo1
Foo2
Foo4
*/
}
val := reflect.ValueOf(g)
for i:=0;i<val.NumMethod();i++{
t := val.Method(i)
//t.Call调用函数,里面接收一个[]Value,返回一个[]Value
t.Call([]reflect.Value{})
/*
Foo1
Foo2
Foo4
*/
}
}
反射修改结构体的字段的值
当我们想修改结构体指定字段的值,用反射该如何实现呢?
package main
import (
"fmt"
"reflect"
)
type girl struct {
Name string
Age int
Gender string
}
func main() {
g := girl{"mashiro", 17, "f"}
//修改值的话,肯定要用ValueOf。因为TypeOf是用来获取字段本身属性的
v := reflect.ValueOf(&g).Elem()
//是可以设置的
fmt.Println(v.FieldByName("Age").CanSet()) //true
//设置值
v.FieldByName("Name").SetString("satori")
//打印发现已经被修改了
fmt.Println(g)
}
以上是关于golang之反射的主要内容,如果未能解决你的问题,请参考以下文章