-
反射(reflect)让我们能在运行期弹指对象的类型信息和内存结构,弥补了静态语言上的不足。
-
反射还是实现元编程的重要手段。
-
和C数据结构一样的,GO对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关的信息的。反射操作所需要的全部信息都源自接口变量。
-
接口变量除存储自身类型外, 还会保存实际对象的类型数据。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
-
这两个反射入口函数,会将任何传入的对象转换为接口类型。
-
在面对类型时,需要区分type和kind。前者辨识真是类型(静态类型),后者表示其基础结构(底层类型)区别。
type x int
func main() {
var a X = 100
t := reflect.TypeOf(a)
fmt.Println(t.Name(), t.kind())
}
- 输出:
x int
- 所以在类型判断上,需选择正确的方式。
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, ta == tc)
fmt.Println(ta.kind() == tc.kind())
}
- 输出:
true false
true
- 除通过实际对象获取类型外,也可直接构造一些基础符合类型。
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
- 传入对象应区分基类型和指针类型,因为他们并不属于同一类型。
func main() {
x := 10
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返回指针,数据,切片,字典或通道的基类型。
func main() {
fmt.Println(reflect.TypeOf(map[string]int()).Elem())
fmt.Println(reflect.TypeOf([]int32{}).Elem())
}
输出:
int
int32
- 只有在获取结构体指针的基类型后,才能遍历它的字段。
type user struct {
name string
age int
}
type manger 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.Fiedle(i)
fmt.Println(f.Name, f.Type, f.Offset)
if f.Annoymous {
for x := 0;x < f.Type.NumField();x++ {
af := f.Type.Field(x)
fmt.Println(" ", af.Name, af.Type)
}
}
}
}
输出:
user main.user 0
name string
age int
title string 24
- 对于匿名字段,可用多级索引(按定义顺序)直接访问。
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
-
FieldByName 不支持多级名称,如有同名遮蔽,需通过匿名字段二次获取。
-
同样的,输出方法集时,一样区分基类型和指针类型。
-
有一点和想想的不用,反射能弹指当前包或外包的非导出结构成员。
-
相对reflect来说,当前包和外包都是“外包”。
-
可用反射提取struct tag ,还能自动分解,。常用于ORM映射,或数据格式验证。
-
辅助判断方法 Implements ConvertibleTo AssignableTo,都是运行期进行动态调用和赋值所必须的。
和type获取类型信息不同value专注于对象实例数据读写。
接口变量会复制对象,且是unaddressable的,所以想修改目标对象,就必须使用指针。
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获取目标对象。因为被接口存储的指针本身是不能寻址和进行设置操作的。
-
不能对非导出字段直接进行设置操纵,无论是当前包还是外包。
-
可通过Interface 方法进行类型推断和转换。
-
复合类型对象设置示例;
-
接口有两种nil状态,这一直是个潜在麻烦。解决方法是用IsNil判断值是否为nil
-
也可用unsafe转换后直接判断iface.data是否为零值。
-
value里的某些方法并未实现ok-idom或返回error,所以得自行判断返回的是否为Zero Value
动态调用方法,谈不上有多麻烦。只需按In列表准备好所需参数即可。
type X struct {}
func (X) Test(x, y int) (int, error) {
return x + y, fmt.Errorf("err:%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
err: 3
- 对于变参来说,用CallSlice要更方便一些。
反射库提供了内置函数make和new的对应操作,其中最有意思的就是makefunc。可用它实现通过模板,适应不同数据类型。
//通用算法函数
func add(args []reflect.Value) (results []reflect.Value) {
if len(args) == 0 {
return nil
}
var ret reflect.Value
switch args[0].Kind() {
case reflect.Int:
n := 0
for_, a := range args {
n += int(a, Int())
}
ret = reflect.ValueOf(n)
case reflect.String:
ss := make([]string, 0, len(args))
for_, s := range args {
ss = append(ss, s.String())
}
ret = reflect.ValueOf(strings.Join(ss, ""))
}
results = append(results, ret)
return
}
//将函数指针参数指向通用算法函数
func makeAdd(fptr interface()) {
fn := reflect.ValueOf(fptr).Elem()
v := reflect.MakeFunc(fn, Type(), add()) //zheshi guanjian
fn.Set(v) //指向通用算法函数
}
func main() {
var int&Add func (x, y int) int
var strAdd func(a, b string) string
makeAdd(&intAdd)
makeAdd(&strAdd)
println(intAdd(100, 200))
println(strAdd("hello,"," world!"))
}
输出:
300
hello, world!