反射(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