天天看點

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 {}

實際類型是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 可以看看這兩篇文章,反射其實是對接口這個資料結構裡面資料的讀取和修改。

繼續閱讀