golang反射总结
简介
反射是建立在类型系统之上的,首先要清楚的是Go是一种静态语言,准确的说变量在编译器会有一种固定的明确的类型。
type MyInt int
var i int
var j MyInt
上面例子中,i是int类型,j是MyInt类型,二者底层虽然都是int类型,但是若不进行转换是无法直接赋值的。
另一个需要事先了解的接口,接口代表一组固定的方法。一个接口可以存储任何确定的非接口类型的值,只要这种类型的值实现了接口对应的方法,如下
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
最后一个重要的点是空接口interface{}
。因为他没有方法,所以他可以存储任何具体的值。
关于go语言的反射非常重要的一点是他和接口是密切相关的。每一个接口变量都存储着一个二元组(value, type)
,其中value表示赋给变量的具体值,type表示该值的类型描述。举例如下:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
在上面代码中,r的二元组实际是(tty, *os.File)
.需要注意的是虽然r定义的是Reader类型,但是type要求的是对值的完整类型描述,也就是具体类型,而不是接口类型。
从接口值获取反射对象
简单来说,反射是一种检查存储在接口变量中的类型和值的机制。所以在go语言的reflect包中提供了这样两种类型:Type和Value。也就分别代表接口变量中存储的那个二元组。分别使用reflect.TypeOf
和reflect.ValueOf
去获取。如下
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
fmt.Println("value:", reflect.ValueOf(x))
}
输出:
type: float64
value: 3.4
Type()
Type与Value都有大量方法,首先Value由一个Type()方法可以获取Type对象
var x float64 = 3.4
xValue := reflect.ValueOf(x)
fmt.Println(xValue.Type())
Kind()
Type与Value都还有一个Kind方法,他返回与各常量,用来表示存储项的类型,如Uint, Float64, Slice等
func main() {
var x float64 = 3.4
xValue := reflect.ValueOf(x)
xType := xValue.Type()
fmt.Println(xValue.Kind())
fmt.Println(xType.Kind())
}
都输出float64
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
Ptr
Slice
String
Struct
UnsafePointer
)
Kind表示的实际上就是底层类型,虽然我们可以定义的类型由无数种,但都是基于某种底层类型,如下
type myInt int
func main() {
var x myInt = 5
xValue := reflect.ValueOf(x)
fmt.Println(xValue.Kind())
}
输出:int
Xxxx
此外Value还有一组以类型命名的方法,如Bool(),Int()等,可以让我们取Value表示的具体的值。这里有一点需要注意的是,虽然我们直接将Value类型传递给fmt也可以直接获得值,那是因为fmt会帮我们做特别处理,若要获得真正的值还要借助这一组方法,这些方法返回的都是基本的类型。另外,如果用错类型会报错,如原本是float,我们却用int取值,就会报错
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("value:", v.Int())
}
SetXxx
Value还有一组Set方法,如SetInt,SetFloat等,但是使用起来有许多需要注意的,见后文详细分析。
从反射对象获取接口值
前面说的是给定一个接口值获得反射对象(Type、Value),这里我们还可以反过来
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
y:=v.Interface().(float64)
fmt.Println(y)
}
进一步的,由于println方法接受interface参数,我们甚至可以这样
fmt.Println(v.Interface())
修改反射对象
首先通过反射对象修改一个值,最重要的前提是该值必须是可设置的
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
上面一段代码必然会可报错,回顾刚才那句话,能修改的前提是该值是可设置的。这里用CanSet()
方法检测,结果是false。
关于可设置性实际上类似于可寻址,通过看报错信息就可以知道panic: reflect: reflect.Value.SetFloat using unaddressable value
。回顾刚才那段代码,我们通过调用ValueOf方法获得一个Value对象,ValueOf传入的是x的一个副本,这就类似c语言中的一个经典问题:值传递和引用传递。当是值传递是,给方法的仅仅是一个副本,所以不能修改对象本身,既然这样我们试试传入地址呢?
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println(p.Type())
fmt.Println(p.CanSet())
输出:*float64,false
很遗憾还是不可设置,但是注意这里的不可设置是不可以修改*p(指针)本身。我们当然不要修改指针本身,我们要修改指针指向的值,这是就需要Elem方法
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println(p.Type())
fmt.Println(p.CanSet())
v := p.Elem()
fmt.Println(v.Type())
fmt.Println(v.CanSet())
}
输出:
*float64
false
float64
true
通过调用Elem方法我们又获得了一个Value对象,但是这个对象就不是指针了,而且是可修改的,这时就可以调用SetXxx方法
func main() {
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(5.1)
fmt.Println(v.Interface())
fmt.Println(x)
}
输出:5.1 5.1
总结一下,虽然是到了反射的层面,也依旧是指针才能修改对象。
结构体
前面讲的都是基本类型,关于结构体也是一样的原理,下面给一个反射结构体的例子
获取结构体成员
type T struct {
A int
B string
}
func main() {
t := T{23, "skidoo"}
tType:=reflect.TypeOf(t)
tValue:=reflect.ValueOf(t)
for i := 0; i<tType.NumField();i++ {
tf:=tType.Field(i)
f:=tValue.Field(i)
fmt.Printf("%d : %s(%s) %s=%v\n",i,tf.Name,f.Kind(),f.Type(),f.Interface())
}
}
注意这里用了两个Field方法,都是通过编号取对应的成员。但是主要区别是,Type的Field方法返回的是一个StructField对象,其中包含着字段的名称,包路径,类型,标签(在序列化与反序列化操作中,如json中很重要)等内容。而Value的Field方法返回的就是成员的一个Value对象,可进一步做反射操作
获取结构体方法
type T struct {
A int
B string
}
func(t T)f1(){
}
func(t T)F2(){
}
func (t T)F3(i int)int {
return 0
}
func(t *T)F4(){
}
func main() {
a:=&T{5,"hello"}
b:=T{5,"hello"}
tType1 := reflect.TypeOf(a)
tType2 := reflect.TypeOf(b)
for i:=0; i<tType1.NumMethod();i++ {
m:=tType1.Method(i)
fmt.Printf("该结构体第%d个方法名为%v,类型是%v\n",i,m.Name,m.Type)
fnType:=m.Func.Type()
for j := 0; j < fnType.NumIn(); j++ {
fmt.Printf("\t 该方法第%d个参数类型为%v\n",j,fnType.In(j))
}
for j := 0; j < fnType.NumOut(); j++ {
fmt.Printf("\t 该方法第%d个返回值类型为%v\n",j,fnType.Out(j))
}
}
fmt.Println("-----------")
for i:=0; i<tType2.NumMethod();i++ {
m:=tType2.Method(i)
fmt.Printf("该结构体第%d个方法名为%v,类型是%v\n",i,m.Name,m.Type)
fnType:=m.Func.Type()
for j := 0; j < fnType.NumIn(); j++ {
fmt.Printf("\t 该方法第%d个参数类型为%v\n",j,fnType.In(j))
}
for j := 0; j < fnType.NumOut(); j++ {
fmt.Printf("\t 该方法第%d个返回值类型为%v\n",j,fnType.Out(j))
}
}
}
输出:
该结构体第0个方法名为F2,类型是func(*main.T)
该方法第0个参数类型为*main.T
该结构体第1个方法名为F3,类型是func(*main.T, int) int
该方法第0个参数类型为*main.T
该方法第1个参数类型为int
该方法第0个返回值类型为int
该结构体第2个方法名为F4,类型是func(*main.T)
该方法第0个参数类型为*main.T
-----------
该结构体第0个方法名为F2,类型是func(main.T)
该方法第0个参数类型为main.T
该结构体第1个方法名为F3,类型是func(main.T, int) int
该方法第0个参数类型为main.T
该方法第1个参数类型为int
该方法第0个返回值类型为int
基本流程还是先反射出Type对象,然后用NumMethod()获取结构体方法数(只包含可导出方法),接着用Method方法获取某个方法的Method对象,Method包含着方法的的基本描述,但是不含参数与返回值。要获得这两组内容需要先获取Method的Func成员,他是一个Value对象,他的NumIn()方法表示参数个数,In()表示某个参数的类型。同理,NumOut()表示返回值个数,Out()表示某个返回值的类型。暂时无法获取到参数名,因为在调用是参数名是无关紧要的,只要按照参数类型传值即可。
还有一点需要注意的是,对指针类型对象和对值类型对象反射获取到的方法数是不同的,指针类型可以获取所有可导出方法,但是值类型只能获取那些值方法。
另外所有方法的参数第一个参数都是结构体对象本身,这也就间接的解释了结构体方法的特点。
利用反射实例化
func main() {
var a int
t := reflect.TypeOf(a)
n := reflect.New(t)
n.Elem().SetInt(10)
fmt.Println(a)
fmt.Println(n.Elem().Interface())
}
首先要反射出一种Type对象,然后用new方法新建一个Value对象,注意这实际上是指针类型的,所以要用前面讨论过的方法进行赋值。再给一个实例化结构体的
func main() {
var t T
tType := reflect.TypeOf(t)
n := reflect.New(tType)
n.Elem().Field(0).SetInt(10)
n.Elem().Field(1).SetString("hello")
nt := n.Elem().Interface().(T)
fmt.Println(nt.A,nt.B)
}
利用反射调用方法
func main() {
v:=reflect.ValueOf(add)
params:=[]reflect.Value{reflect.ValueOf(10),reflect.ValueOf(20)}
r :=v.Call(params)
fmt.Println(r[0].Int())
}
func add(a,b int)int {
return a+b;
}
首先反射出方法的value对象,然后构造参数数组,接着调用call方法调用,返回值是一个数组,表示方法的返回值数组。再给一个反射调用结构体方法的例子
type T struct {
A int
B string
}
func(t *T)Add(i int)int{
return t.A+i;
}
func main() {
t:=&T{10,"hello"}
tType:=reflect.TypeOf(t)
f:=tType.Method(0).Func
fmt.Println(f.Call([]reflect.Value{reflect.ValueOf(t),reflect.ValueOf(10)})[0].Int())
}
注意,凡是属于结构体的方法,默认第一个参数都是结构体对象本身。
题图来自unsplash:https://unsplash.com/photos/wAn4RfmXtxU