GO 中的反射

1、类型

在面对类型时,我们要区分Type 和 Kind 。前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别。

package main

import (
	"fmt"
	"reflect"
)

type X int

func main()  {
	var a X = 100
	t := reflect.TypeOf(a)
	fmt.Println(t.Name(), t.Kind())

}

输出:
X int

package main

import (
	"fmt"
	"reflect"
)

type X int
type Y int

func main()  {
	var a, b X = 100, 200
	var c Y = 300
	ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
	fmt.Println(ta == tb, tb == tc)
	fmt.Println(ta.Kind() == tc.Kind())
}

输出:
true false
true
除通过实际对象获取类型外,也可直接构造一些基础复合类型。

package main

import (
	"fmt"
	"reflect"
)

func main()  {
	a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
	m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
	fmt.Println(a, m)
}

输出:
[10]uint8 map[string]int
传入对象应区分基类型和指针类型,因为它们并不属于同一类型。

package main

import (
	"fmt"
	"reflect"
)

func main()  {
	x := 100
	tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
	fmt.Println(tx, tp, tx == tp)
	fmt.Println(tx.Kind(),tp.Kind())
	fmt.Println(tx == tp.Elem())
}

输出:
int *int false
int ptr
true
方法 Elem 返回指针、数组、切片、字典(值)或通道的基类型。

package main

import (
	"fmt"
	"reflect"
)

func main()  {
	fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
	fmt.Println(reflect.TypeOf([]int32{}).Elem())
}

输出:
int
int32
只有在获取结构体指针的基类型后,才能遍历它的字段。

package main

import (
	"fmt"
	"reflect"
)

type user struct {
	name 	string
	age 	int
}

type manager struct {
	user
	title 	string
}

func main()  {
	var m manager
	t := reflect.TypeOf(&m)
	if t.Kind() == reflect.Ptr {   // 获取指针的基类型
		t = t.Elem()
	}
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Println(f.Name, f.Type, f.Offset)
		if f.Anonymous {  // 输出匿名字段
			for t := 0; t < f.Type.NumField(); t++ {
				af := f.Type.Field(t)
				fmt.Println(" ", af.Name, af.Type)
			}
		}
	}
}

输出:
user main.user 0
name string
age int
title string 24
对于多级索引,可用多级索引(按定义顺序)直接访问。

package main

import (
	"fmt"
	"reflect"
)

type user struct {
	name 	string
	age 	int
}

type manager struct {
	user
	title 	string
}

func main()  {
	var m manager
	t := reflect.TypeOf(m)
	name, _ := t.FieldByName("name")  // 按名称查找
	fmt.Println(name.Name, name.Type)
	age := t.FieldByIndex([]int{0, 1})   // 按多级索引查找
	fmt.Println(age.Name, age.Type)
}

输出:
name string
age int
FileByName 不支持多级名称,如有同名遮蔽,须通过匿名字段二次获取。
同样地,输出方法集时,一样区分基类型和指针类型。
同样地,输出方法集时,一样区分基类型和指针类型。

package main

import (
	"fmt"
	"reflect"
)

type A int
type B struct {
	A
}

func (A) Av()  {}

func (*A) Ap()  {}

func (B) Bv()  {}

func (*B) Bp()  {}

func main()  {
	var b B
	t := reflect.TypeOf(&b)
	s := []reflect.Type{t, t.Elem()}
	for _, t := range s {
		fmt.Println(t, ":")
		fmt.Println(t.NumMethod())
		for i := 0; i < t.NumMethod(); i++ {
			fmt.Println(" ", t.Method(i))
		}
	}

}

输出:
*main.B :
4
{Ap func(*main.B) <func(*main.B) Value> 0}
{Av func(*main.B) <func(*main.B) Value> 1}
{Bp func(*main.B) <func(*main.B) Value> 2}
{Bv func(*main.B) <func(*main.B) Value> 3}
main.B :
2
{Av func(main.B) <func(main.B) Value> 0}
{Bv func(main.B) <func(main.B) Value> 1}

有一点和想象的不同,反射能探知当前包或外包的非导出结构成员,不能探出非导出方法。

package main

import (
	"fmt"
	"net/http"
	"reflect"
)

func main()  {
	var s http.Server
	t := reflect.TypeOf(s)
	for i := 0; i < t.NumField(); i++ {
		fmt.Println(t.Field(i).Name)
	}
}

输出:
Addr
Handler
TLSConfig
ReadTimeout
ReadHeaderTimeout
WriteTimeout
IdleTimeout
MaxHeaderBytes
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
inShutdown
nextProtoOnce
nextProtoErr
mu
listeners
activeConn
doneChan
onShutdown
相对 reflect而言,当前包和外包都是“外包”。
可用反射提取 struct tag,还能自动分解。其常用于ORM 映射,或数据格式验证。

package main

import (
	"fmt"
	"reflect"
)

type user struct {
	name	string	`field:"name" type:"varchar(50)"`
	age 	int		`field:"age" type:"int"`
}

func main()  {
	var u user
	t := reflect.TypeOf(u)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Printf("%s: %s  %s\n", f.Name, f.Tag.Get("field"), f.Tag.Get("type"))
	}

}

输出:
name: name varchar(50)
age: age int

2、 值

接口变量会复制对象,且是 unaddressable的,所以要想修改目标对象,就必须使用指针。

package main

import (
	"fmt"
	"reflect"
)

func main()  {
	a := 100
	va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()
	fmt.Println(va.CanAddr(), va.CanSet())
	fmt.Println(vp.CanAddr(), vp.CanSet())
}

输出:
false false
true true
就算传入指针,一样需要通过 Elem 获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的。
注意,不能对非导出字段直接进行设置操作,无论是当前包还是外包。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type User struct {
	Name 	string
	code 	int
}

func main()  {
	p := new(User)
	v := reflect.ValueOf(p).Elem()

	name := v.FieldByName("Name")
	code := v.FieldByName("code")
	fmt.Printf("Name: canaddr = %v, canset = %v\n", name.CanAddr(), name.CanSet())
	fmt.Printf("code: canaddr = %v, canset = %v\n", code.CanAddr(), code.CanSet())

	if name.CanSet() {
		name.SetString("Tom")
	}
	if code.CanAddr() {
		*(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
	}
	fmt.Printf("%+v\n", *p)
}

输出:
Name: canaddr = true, canset = true
code: canaddr = true, canset = false
{Name:Tom code:100}

Value.Pointer 和 Value.Int 等方法类似,将Value.data 存储的数据转换为指针,目标必须是指针类型。而 UnsafeAddr 返回任何 CanAddr Value.data 地址(相当于 & 取地址操作),比如Elem 后的 Value,以及字段成员地址。
以结构体里的指针类型字段为例,Pointer 返回该字段所保存的地址,而 UnsafeAddr 返回该字段自身的地址(结构对象地址+偏移量)。

3、方法

动态调用方法,只需要按 In 列表准备好所需参数即可。

package main

import (
	"fmt"
	"reflect"
)

type X struct {

}

func (X) Test(x, y int) (int, error) {
	return x + y, fmt.Errorf("error: %d", x + y)
}

func main()  {
	var a X
	v := reflect.ValueOf(&a)
	m := v.MethodByName("Test")
	in := []reflect.Value{
		reflect.ValueOf(1),
		reflect.ValueOf(2),
	}
	out := m.Call(in)
	for _, v := range out {
		fmt.Println(v)
	}
}

输出:
3
error: 3

对于变参来说,用 CallSlice 要更方便一些。

package main

import (
	"fmt"
	"reflect"
)

type X struct {

}

func (X) Format(s string, a ...interface{}) string {
	return fmt.Sprintf(s, a...)
}

func main()  {
	var a X
	v := reflect.ValueOf(&a)
	m := v.MethodByName("Format")
	in := []reflect.Value{
		reflect.ValueOf("%s = %d"),
		reflect.ValueOf("x"),
		reflect.ValueOf(100),
	}
	out := m.Call(in)
	fmt.Println(out)

	out = m.CallSlice([]reflect.Value{
		reflect.ValueOf("%s = %d"),
		reflect.ValueOf([]interface{}{"x", 100}),
	})
	fmt.Println(out)
}

输出:
[x = 100]
[x = 100]

4、性能

反射在带来“方便”的同时,也造成了性能的损失,如对性能要求较高,须谨慎使用反射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值