反射是程序校验自己数据结构和类型的一种机制。文章尝试解释Golang的反射机制工作原理,每种编程语言的反射模型都是不同的,有很多语言甚至都不支持反射。
在将反射之前需要先介绍下接口interface,因为Golang的反射实现是基于interface的。Golang是静态类型语言,每个变量拥有一个静态类型,在编译器就已经确定,例如int,float32,*MyType,[]byte等等。如果我们定义:
typeMyIntint variint varjMyIntint类型的I和MyInt类型的j是不同类型的变量,在没有限制类型转换的情况下它们不能相互赋值,即便它们的底层类型是一样的。
接口interface类型是最重要的一种数据类型,代表的一些方法的集合。interface变量可以存储任意的数据类型,只要该数据类型实现了interface的方法集合。例如io包的io.Reader和io.Writer:
//ReaderistheinterfacethatwrapsthebasicReadmethod. typeReaderinterface{ Read(p[]byte)(nint,errerror) } //WriteristheinterfacethatwrapsthebasicWritemethod. typeWriterinterface{ Write(p[]byte)(nint,errerror) }任意实现了Read方法的类型都是Reader类型,也就是说可以赋值给Reader接口,换句话说就是Readerinterface可以存储任意的实现了Read方法的类型:
varrio.Reader r=os.Stdin r=bufio.NewReader(r) r=new(bytes.Buffer) //andsoon需要明确的是无论上述变量r实际存储的是什么类型,r的类型永远都是io.Reader,这就是为什么说Golang是静态类型编程语言,因为r声明时是io.Reader,在编译期就已经明确了类型。
Interface一个特别重要的示例是空接口:
interface{}它代表一个空的方法集合,因为任意类型值都有0个多少多个方法,所以空的接口interface{}可以存储任意类型值。
有些人说Golang的interface是动态类型,其实是种误解。接口是静态类型,interface变量定义时就声明了一种静态类型,即便interface存储的值在运行时会改变类型,但是interface的类型是一定的。
一个interface类型变量会存储一对数据,具体类型的值和值的具体类型(value,concretetype)。例如:
varrio.Reader tty,err:=os.OpenFile("/dev/tty",os.O_RDWR,0) iferr!=nil{ returnnil,err } r=tty上述的interface变量I会存储一对数据(tty,*os.File)。需要注意的是*os.File类型不止单单实现了Read方法,还实现了其他方法,比如Write方法。即便interface类型变量i值提供了访问Read的方法,i还是携带了*os.File变量的所有类型信息。所以可以将i转换为io.Writer类型:
varwio.Writer w=r.(io.Writer)上述的表达式是一个类型断言,断言r也实现了io.Writer,所以可以赋值给w,否则会panic。完成赋值后,w会携带一对值(tty,*os.File),和r一样的一对值。接口的静态类型决定了上述的tty能够调用的方法,即便它实际上包含了更多的方法。
也可以将它赋值给空接口:
varemptyinterface{} empty=w空接口empty也携带同样的对值(tty,*os.File)。因为任意的类型都是空接口所以不用转换。
从本质上讲,反射是校验接口存储(value,concretetype)值对的一种机制。分别对应的reflect包的Value和Type类型。通过Value和Type类型可以访问到interface变量的储存内容,reflect.TypeOf和reflect.ValueOf将会返回interface变量的reflect.Type和reflect.Value类型值。
从TypeOf开始:
packagemain import( "fmt" "reflect" ) funcmain(){ varxfloat64=3.4 fmt.Println("type:",reflect.TypeOf(x)) }结果将会输出:
type:float64你可能会有疑问,反射是基于interface,那么这里的interface在哪儿呢?这就需要了解TypeOf的定义:
//TypeOfreturnsthereflectionTypeofthevalueintheinterface{}. funcTypeOf(iinterface{})Type也就是说TypeOf会用interface{}把参数储存起来,然后reflect.TypeOf再从interface{}中获取信息。
同理ValueOf的函数定义为:
//ValueOfreturnsanewValueinitializedtotheconcretevalue //storedintheinterfacei.ValueOf(nil)returnsthezeroValue. funcValueOf(iinterface{})Value示例:
varxfloat64=3.4 v:=reflect.ValueOf(x) fmt.Println("type:",v.Type()) fmt.Println("kindisfloat64:",v.Kind()==reflect.Float64) fmt.Println("value:",v.Float())结果输出:
type:float64 kindisfloat64:true value:3.4所以我们可以得出反射的第一条规则是:反射对象是从接口值获取的。
规则2:可以从反射对象中获取接口值。
利用reflect.Value的Interface方法可以获得传递过来的空接口interface{}:
//Interfacereturnsvsvalueasaninterface{}. func(vValue)Interface()interface{}示例:
y:=v.Interface().(float64)//ywillhavetypefloat64. fmt.Println(y)规则3:通过反射对象的set方法可以修改实际储存的变量,前提是存储的变量是可以被修改的。
反射定义变量是可以被修改的(settable)条件是传递变量的指针,因为如果是值传递的话,反射对象set方法改变的是一份拷贝,所以会显得怪异而且没有意义,所以干脆就将值传递的情况定义为不可修改的,如果尝试修改就会触发panic。
示例:
varxfloat64=3.4 v:=reflect.ValueOf(x) v.SetFloat(7.1)//Error:willpanic报错如下:
panic:reflect.Value.SetFloatusingunaddressablevalue可以通过反射对象Value的CanSet方法判断是否是可修改的:
varxfloat64=3.4 v:=reflect.ValueOf(x) fmt.Println("settabilityofv:",v.CanSet())输出:
settabilityofv:false可被修改的情况:
varxfloat64=3.4 p:=reflect.ValueOf(&x)//Note:taketheaddressofx. fmt.Println("typeofp:",p.Type()) fmt.Println("settabilityofp:",p.CanSet())输出:
typeofp:*float64 settabilityofp:false反射对象p是不可被修改的,因为p不是我们想要修改的,*p才是。调用Value的Elem方法可以获取p指向的内容,并且内容储存在Value对象中:
v:=p.Elem() fmt.Println("settabilityofv:",v.CanSet())输出:
settabilityofv:true示例:
v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)输出:
7.1 7.1只要有结构体的地址我们就可以用反射修改结构体的内容。下面是个简单的示例:
typeTstruct{ Aint Bstring } t:=T{23,"skidoo"} s:=reflect.ValueOf(&t).Elem() typeOfT:=s.Type() fori:=0;i<s.NumField();i++{ f:=s.Field(i) fmt.Printf("%d:%s%s=%v\n",i, typeOfT.Field(i).Name,f.Type(),f.Interface()) }程序输出:
0:Aint=23 1:Bstring=skidoo修改:
s.Field(0).SetInt(77) s.Field(1).SetString("SunsetStrip") fmt.Println("tisnow",t)程序输出:
tisnow{77SunsetStrip}所以反射的三条规则总结如下:
规则1:反射对象是从接口值获取的。
规则2:可以从反射对象中获取接口值。
规则3:通过反射对象的set方法可以修改实际储存的settable变量
由于Json的序列化(编码)和反序列化(解码)都会用到反射,所以这里放在一起讲解。
Json编码可以用Marshal函数完成Json编码:
funcMarshal(vinterface{})([]byte,error)给定一个Golang的结构体Message:
typeMessagestruct{ Namestring Bodystring Timeint64 }Message的实例m为:
m:=Message{"Alice","Hello",1294706395881547000}Marshal编码Json:
b,err:=json.Marshal(m)如果工作正常,err为nil,b为[]byte类型的Json字符串:
b==[]byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)Json编码规则:
1.Json对象只支持string作为key;所以想要编码Golangmap类型必须是map[stirng]T,其中T表示Golang支持的任意类型。
2.Channel,complex和函数类型不能被编码
3.循环引用嵌套的结构体不支持,他们会造成Marshal进入一个未知的循环体重
4.指针将会被编码指向的内容本身,如果指针是nil将会是null
Json解码可以用Unmarshal解码Json数据:
funcUnmarshal(data[]byte,vinterface{})error首先我们必须要先创建解码数据存储的变量:
varmMessage然后传递变量的指针(参考反射规则3):
err:=json.Unmarshal(b,&m)如果b包含可用的Json并且适合m,那么err将会是nil,b的数据会被存储在m中,就好像下面的赋值一样:
m=Message{ Name:"Alice", Body:"Hello", Time:1294706395881547000, }Unmarshal是怎么识别要存储的解码字段的呢?例如Json的一个Key为”Foo”,Unmarshal会找根据下面的规则顺序匹配:
1.找名为“Foo”的字段tag
2.找名为“Foo”,”FOO”或者“FoO”的字段名称
再看下面的Json数据解码会匹配到Golang的什么数据类型呢:
b:=[]byte(`{"Name":"Bob","Food":"Pickle"}`) varmMessage err:=json.Unmarshal(b,&m)Unmarshal只会解码它认识的字段。在这个例子中,只有Name字段出现在m中,所以Food字段会被忽略。当你想在一个大的Json数据中提取你要想的部分字段时,该特性是非常有用的。这意味着你不需要关心Json的所有字段,只需要关心你要用到的字段即可。
json包会用map[string]interface{}存储Json对象,用[]interface{}存储数组。当UnmarshalJson对象作为interface{}值时,默认Golang的concretetype为:
Jsonbooleans类型默认为bool
Json数字默认为float64
Jsonstrings默认为string
Jsonnull默认为nil
示例:
b:=[]byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) varfinterface{} err:=json.Unmarshal(b,&f)相对于下面的赋值操作:
f=map[string]interface{}{ "Name":"Wednesday", "Age":6, "Parents":[]interface{}{ "Gomez", "Morticia", }, }如果想要访问f的底层map[string]interface{}数据结构需要断言:
m:=f.(map[string]interface{})然后遍历map接着访问其他成员:
fork,v:=rangem{ switchvv:=v.(type){ casestring: fmt.Println(k,"isstring",vv) casefloat64: fmt.Println(k,"isfloat64",vv) case[]interface{}: fmt.Println(k,"isanarray:") fori,u:=rangevv{ fmt.Println(i,u) } default: fmt.Println(k,"isofatypeIdontknowhowtohandle") } }上述示例中,可以定义一个结构体来存储:
typeFamilyMemberstruct{ Namestring Ageint Parents[]string } varmFamilyMember err:=json.Unmarshal(b,&m)Unmarshal数据进入FamilyMembear值时,会自动给nil切片分配内存,同理如果有指针,map也会自动分配内存。
文章介绍了interface、reflection、json,其中reflection是基于interface实现的,而json的编码和解码用到了reflection。
原文链接: