golang 反射解惑

原文链接:https://www.cnblogs.com/kidyuan/p/9736566.html

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

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.Valuereflect.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 可以看看这两篇文章,反射其实是对接口这个数据结构里面数据的读取和修改。

转载于:https://www.cnblogs.com/kidyuan/p/9736566.html

你可能感兴趣的:(golang 反射解惑)