golang 反射解惑
Type和Kind的區別
直接看例子:
type Myint int type Person struct { } func main() { var myint Myint= 1 var person Person= Person{} s := 1 var intPtr =&s mySlice := []string{} myMap := map[string]int{} myintType := reflect.TypeOf(myint) personType := reflect.TypeOf(person) intPtrType := reflect.TypeOf(intPtr) mySliceType := reflect.TypeOf(mySlice) myMapType := reflect.TypeOf(myMap) myintKind := myintType.Kind() personKind := personType.Kind() intPtrKind := intPtrType.Kind() mySliceKind := mySliceType.Kind() myMapKind := myMapType.Kind() fmt.Printf("myint Type():%s , Kind():%s\n",myintType,myintKind) fmt.Printf("person Type():%s , Kind():%s\n",personType,personKind) fmt.Printf("intPtr Type():%s , Kind():%s\n",intPtrType,intPtrKind) fmt.Printf("mySlice Type():%s , Kind():%s\n",mySliceType,mySliceKind) fmt.Printf("myMap Type():%s , Kind():%s\n",myMapType,myMapKind) }
運行結果如下:
myint Type():main.Myint , Kind():int
person Type():main.Person , Kind():struct
intPtr Type():*int , Kind():ptr
mySlice Type():[]string , Kind():slice
myMap Type():map[string]int , Kind():map
這裏看出來Type是實際類型,Kind是底層類型。實際類型和底層類型是我給起的名字。比如type Myint int
的實際類型是Myint,底層類型是int。type Person struct {}
XXX
的指針,實際類型就是XXX
底層類型是指針。可以把實際類型理解成我們聲明一個變量時候所用的類型,底層類型是golang提供的原始類型。
官方文檔對Kind的解釋是:
A Kind represents the specific kind of type that a Type represents.
翻譯過來就是Kind是Type的類型。總之記住Kind比Type更加原始就好。
下面是go語言支持的所有Kind:
const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer )
Elm方法
Type接口和Value接口都有Elem() 方法。
Type接口的Elem() 方法原型是Elem() Type
,註釋:
// Elem returns a type‘s element type.
// It panics if the type‘s Kind is not Array, Chan, Map, Ptr, or Slice.
Elem返回的是調用者的元素的類型,如果調用者的Kind不是Array, Chan, Map, Ptr, Slice.那麽會拋出異常。
看個Demo:
func main() {}
s := 1
var intPtr =&s
mySlice := []string{}
myMap := map[string]int{}
myArray:= [...]string{}
var myChan chan int= make(chan int)
intPtrKind := reflect.TypeOf(intPtr).Elem()
mySliceKind := reflect.TypeOf(mySlice).Elem()
myMapKind := reflect.TypeOf(myMap).Elem()
myArrayKind := reflect.TypeOf(myArray).Elem()
myChanKind := reflect.TypeOf(myChan).Elem()
fmt.Printf("Elem():%s\n",intPtrKind)
fmt.Printf("Elem():%s\n",mySliceKind)
fmt.Printf("Elem():%s\n",myMapKind)
fmt.Printf("Elem():%s\n",myArrayKind)
fmt.Printf("Elem():%s\n",myChanElem)
}
/*打印結果
intPtr Type():*int , Elem():int
mySlice Type():[]string , Elem():string
myMap Type():map[string]int , Elem():int
myArray Type():[0]string , Elem():string
myChan Type():chan int , Elem():int
*/
從上面可以看到:
對於指針類型來說Elem的結果是它指向的數據的類型
對於數組和切片類型來說Elem的結果是它存儲的元素的類型
對於Map類型來說Elem的結果是它存儲的Value的類型
對於通道類型來說Elem的結果是通道可以存儲的數據的類型
Value接口的Elem() 方法原型是Elem() Value
,註釋:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v‘s Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
調用方的的Kind必須是Interface或者指針,否則發生panic,Elem函數返回接口包含的值或者指針指向的值。
func main() {
s := 1
var intPtr = &s
intPtrValueElem := reflect.ValueOf(intPtr)
fmt.Println("pointer Kind:",intPtrValueElem.Kind()," Value.Elem():",intPtrValueElem.Elem())
}
/*打印結果
pointer Kind: ptr Value.Elem(): 1
*/
從結果可以看出Value.Elem方法取出來的是指針指向的值。(這裏只舉了一個Kind是Ptr的例子,關於Kind是Interface的具體可以看看這裏In Go, which value’s kind is reflect.Interface? )
如何設置值
這裏直接給幾個例子
1.設置切片:
func main() {
names := make([]string,3)
val := reflect.ValueOf(names)
for i:=0;i<val.Len();i++{
name := fmt.Sprintf("names%d",i)
val.Index(i).SetString(name)
}
fmt.Println(val)
}
//運行結果[names0 names1 names2]
2.設置Map
func main() {
names := make(map[string]int)
val := reflect.ValueOf(names)
val.SetMapIndex(reflect.ValueOf("name1"),reflect.ValueOf(1)) //key=name1,value=name2
val.SetMapIndex(reflect.ValueOf("name2"),reflect.ValueOf(2))
fmt.Println(val) //打印map[name1:1 name2:2]
value1 :=val.MapIndex(reflect.ValueOf("name1"))
value2 :=val.MapIndex(reflect.ValueOf("name2"))
fmt.Println("value1:",value1," value2:",value2) //打印value1: 1 value2: 2
}
3.修改通道類型的值
func main() {
var myChan chan int= make(chan int,1)
myChan <-1
val := reflect.ValueOf(myChan)
fmt.Println(val.Recv()) //接收數據
val.Send(reflect.ValueOf(7))
fmt.Println(<-myChan) //發送數據
}
/*結果:
1 true
7
*/
4.修改基本類型和struct類型
func main() {
//int type
a := 5
val := reflect.ValueOf(&a)
if val.Elem().CanSet() {
val.Elem().SetInt(10)
}
fmt.Println(a)
//string
s := "yuanjize"
str := reflect.ValueOf(&s)
if str.Elem().CanSet() {
str.Elem().SetString("Tom")
}
fmt.Println(s)
// struct
user := &User{UserName: "yuanjize", Passwd: "123", Age: 3}
typ := reflect.TypeOf(user).Elem()
value := reflect.ValueOf(user).Elem() //reflect.ValueOf(user)得到的Value打印出來是指針指向的地址,比如0xff998877。reflect.ValueOf(user).Elem() 得到的Value打印出來的是user的值,所我們後面要修改User的字段,所以這裏要調用Elem方法。
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)
fieldValue := value.Field(i)
fmt.Printf("[BEFORE CHANGE] FieldName:%s , FieldValue:%v canSet:%t \n", fieldType.Name, fieldValue.Interface(), fieldValue.CanSet())
if fieldValue.CanSet() { //這個字段是否可以修改
switch fieldValue.Kind() {
case reflect.Int:
{
fieldValue.SetInt(10)
}
case reflect.String:
{
fieldValue.SetString("dean")
}
default:
fmt.Println("no such type")
}
}
fmt.Printf("[AFTER CHANGE] FieldName:%s , FieldValue:%v canSet:%t \n", fieldType.Name, fieldValue.Interface(), fieldValue.CanSet())
}
}
上面提供了幾種反射golang數據類型的例子,可以發現對於map,slice和chan這幾種引用類型來說我們在調用reflect.Value
函數的時候只需傳入相應類型的變量就可以,但是對於基本類型和struct類型必須傳入對應了類型的指針類型才可以在後面對值進行修改,否則在嘗試修改值的時候會報panic。
在修改值之前可以先調用func (v Value) CanSet() bool
函數,他會告訴我們這個變量是否是可以修改的。
如果我們傳入到reflect.Value
的類型是指針,那麽我們要在獲得reflect.Value
和reflect.Type
變量之後調用相應的Elem
方法才能獲得指針指向的對象,這樣才能對對象進行修改。
總結一下:要想通過反射改變類型的值,如果想要改變的是引用類型那麽就直接傳遞引用類型,如果要改變的是非引用類型那麽就需要傳遞該類型的指針,這個規則和通過函數調用修改函數變量的方法是一樣的。
仿照Gin框架的bind部分寫的一個練習
Odm函數會把form參數中存放的字段一一映射到structPtr指向的結構體中。
/*
使用例子:
type Person struct{
Name string `form:"name"`
Age int `form:"age"`
}
person := &Person{}
mMap :=map[string]string}{}
mMap["name"]="yuanjize"
mMap["age"] = "22"
Odm(person,mMap)
*/
func Odm(structPtr interface{},form map[string][]string) {
typ := reflect.TypeOf(structPtr)
fmt.Println("------------------->:",typ)
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct{
panic("first Param must be a pointer of struct")
}
val := reflect.ValueOf(structPtr).Elem()
typ = typ.Elem()
for i:=0;i< val.NumField();i++{
fieldValue := val.Field(i)
fieldType := typ.Field(i)
if !fieldValue.CanSet(){
fmt.Printf("field:%s can‘t be set",fieldType.Name)
continue
}
fieldKind := fieldValue.Kind()
if fieldKind == reflect.Struct{
Odm(fieldValue.Addr().Interface(),form)
}else{
tag := fieldType.Tag.Get("form")
if tag == ""{
fmt.Printf("no such Named:%s Field in Form \n",tag)
continue
}
if fieldKind == reflect.Slice{
//1.get type of ele 2.make s slice 3.fill slice
length := len(form[tag])
if length <=0{
continue
}
itemType := fieldType.Type.Elem().Kind()
slice := reflect.MakeSlice(fieldType.Type,length,length)
for i,v:=range form[tag]{
setValue(itemType,slice.Index(i),v)
}
fieldValue.Set(slice)
}else{
//no slice or struct,find the field and write
if formVal,ok := form[tag];ok{
setValue(fieldKind,fieldValue,formVal[0])
}else{
fmt.Printf("no such Named:%s Field in Form \n",tag)
}
}
}
}
}
func setValue(kind reflect.Kind,field reflect.Value,value string){
switch kind {
case reflect.Int:{
setInts(field,value,0)
}
case reflect.Int8:{
setInts(field,value,8)
}
case reflect.Int16:{
setInts(field,value,16)
}
case reflect.Int32:{
setInts(field,value,32)
}
case reflect.Int64:{
setInts(field,value,64)
}
case reflect.Uint:{
setUints(field,value,0)
}
case reflect.Uint8:{
setUints(field,value,8)
}
case reflect.Uint16:{
setUints(field,value,16)
}
case reflect.Uint32:{
setUints(field,value,32)
}
case reflect.Uint64:{
setUints(field,value,64)
}
case reflect.Bool:{
setBool(field,value)
}
case reflect.String:{
setString(field,value)
}
case reflect.Float32:{
setFloat(field,value,32)
}
case reflect.Float64:{
setFloat(field,value,64)
}
default:
fmt.Println("undefine type :",kind)
}
}
func setInts(field reflect.Value,value string,bitSize int) {
val,_ := strconv.ParseInt(value,10,bitSize)
field.SetInt(val)
}
func setUints(field reflect.Value,value string,bitSize int) {
val,_ := strconv.ParseUint(value,10,bitSize)
field.SetUint(val)
}
func setBool(field reflect.Value,value string) {
val,_ := strconv.ParseBool(value)
field.SetBool(val)
}
func setString(field reflect.Value,value string) {
field.SetString(value)
}
func setFloat(field reflect.Value,value string,bitSize int) {
val,_ := strconv.ParseFloat(value,bitSize)
field.SetFloat(val)
}
參考:
all_test.go 該文件是reflect包的測試文件,在源碼中可以找到,測試文件中提供了很多API的用法。
The Laws of Reflection, Go Data Structures: Interfaces 可以看看這兩篇文章,反射其實是對接口這個數據結構裏面數據的讀取和修改。
golang 反射解惑