反射 | 青训营

35 阅读2分钟

反射(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:=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返回指针、数组、切片、字典(值)或通道的基类型。

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 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 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不支持多级名称,如有同名遮蔽,须通过匿名字段二次获取。

同样地,输出方法集时,一样区分基类型和指针类型。

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, ":") 
  
       for i:=0;i<t.NumMethod();i++ { 
           fmt.Println("  ",t.Method(i)) 
        } 
    } 
}

输出:

*main.B: 
   {ap main func(*main.B) <func(*main.B)Value>0} 
   {av main func(*main.B) <func(*main.B)Value>1} 
   {bp main func(*main.B) <func(*main.B)Value>2} 
   {bv main func(*main.B) <func(*main.B)Value>3} 
  
main.B: 
   {av main func(main.B) <func(main.B)Value>0} 
   {bv main func(main.B) <func(main.B)Value>1}

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

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
ReadTimeout
WriteTimeout
MaxHeaderBytes
TLSConfig
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
nextProtoOnce
nextProtoErr

相对reflect而言,当前包和外包都是“外包”。_

可用反射提取struct tag,还能自动分解。其常用于ORM映射,或数据格式验证。

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

辅助判断方法Implements、ConvertibleTo、AssignableTo都是运行期进行动态调用和赋值所必需的。

type X int
  
func(X)String()string{ 
   return"" 
} 
  
func main() { 
   var a X
   t:=reflect.TypeOf(a) 
  
    //Implements不能直接使用类型作为参数,导致这种用法非常别扭 
   st:=reflect.TypeOf((*fmt.Stringer)(nil)).Elem() 
   fmt.Println(t.Implements(st)) 
  
   it:=reflect.TypeOf(0) 
   fmt.Println(t.ConvertibleTo(it)) 
  
   fmt.Println(t.AssignableTo(st),t.AssignableTo(it)) 
}

输出:

true
true
true false