反射reflection
1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地
2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
3. 反射会将匿名字段作为独立字段(匿名字段本质)
4. 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface
5. 通过反射可以“动态”调用方法
示例一:
举例说明反射使用 TypeOf 和 ValueOf 来取得传入类型的属性字段于方法
package main import ( "fmt" "reflect" ) //定义一个用户结构体 type User struct { Id int Name string Age int } //为接口绑定方法 func (u User) Hello() { fmt.Println("Hello World.") } //定义一个可接受任何类型的函数(空接口的使用规则) func Info(o interface{}) { t := reflect.TypeOf(o) //获取接受到到接口到类型 fmt.Println("Type:", t.Name()) //打印对应类型到名称(这是reflect中自带到) //Kind()方法是得到传入类型到返回类型;下面执行判断传入类型是否为一个结构体 if k := t.Kind(); k != reflect.Struct { fmt.Println("传入的类型有误,请检查!") return } v := reflect.ValueOf(o) //获取接受到到接口类型包含到内容(即其中到属性字段和方法) fmt.Println("Fields:") //如何将其中到所有字段和内容打印出来呢? /** 通过接口类型.NumField 获取当前类型所有字段个数 */ for i := 0; i < t.NumField(); i++ { f := t.Field(i) //取得对应索引的字段 val := v.Field(i).Interface() //取得当前字段对应的内容 fmt.Printf("%6s: %v = %v ", f.Name, f.Type, val) } /** 通过接口类型.NumMethod 获取当前类型所有方法的个数 */ fmt.Println("Method:") for i := 0; i < t.NumMethod(); i++ { m := t.Method(i) //取得对应索引的方法 fmt.Printf("%6s: %v ", m.Name, m.Type) } } func main() { u := User{1, "OK", 12} Info(u) //Info(&u) 如果传入的是结构体的地址或指针(pointer-interface),那么在Info函数中的Kind方法进行判断时就会被拦截返回 }
运行结果如下:
Type: User Fields: Id: int = 1 Name: string = OK Age: int = 12 Method: Hello: func(main.User)
示例二:
如何通过反射得道结构当中匿名或者嵌入字段
package main import ( "fmt" "reflect" ) //定义一个用户结构体 type User struct { Id int Name string Age int } type Manager struct { User //定义了一个匿名引用 title string } func main() { m := Manager{User: User{1, "OK", 15}, title: "123"} t := reflect.TypeOf(m) //取得类型中的字段是否为匿名字段 fmt.Printf("%6v ", t.Field(0)) /** 打印内容:{User main.User 0 [ 0] true},其中true表示是匿名类型 那么想要取匿名类型中的字段又该怎么取呢?这里需要使用序号组,传入要取的切片即可 */ fmt.Printf("%v ", t.FieldByIndex([]int{0, 0})) /** 其中上面切片传入的是{0, 0}, 1. 第一个0表示当前结构Manager取匿名User是第一个即为0 2. 第二个0表示取得的结构User中要取第一个元素Id相对于User来说也是第一个即为0,如果要取Name则需传入[]int{0, 1} 那么既然可以取出来内容,那么我们就可以尝试着进行修改,怎么做呢? */ tchage := reflect.ValueOf(&m) //想要修改和我们之前所说的传入值类型和指针类型是一致的,要想修改需要传入对应指针类型 tchage.Elem().FieldByIndex([]int{0, 0}).SetInt(999) //传入指针需要通过 .Elem() 来取得对应的值内容,之后再想取哪个再继续使用序号组 fmt.Println(tchage.Elem().FieldByName("title")) fmt.Println(tchage) }
运行结果:
{ User main.User 0 [ 0] true} {Id int 0 [0] false} 123 &{{999 OK 15} 123}
示例三:
那么让我们来写一个比较完整的通过反射修改结构体内部字段内容
package main import ( "fmt" "reflect" ) //定义一个用户结构体 type User struct { Id int Name string Age int } func main() { u := User{1, "OK", 13} fmt.Println(u) Set(&u) fmt.Println(u) } //定义一个可以接受任何类型的空接口 func Set(o interface{}) { v := reflect.ValueOf(o) //通过反射修改类型中的内容需要传入指针,为了防止传入有误故在这里进行相关过滤验证判断(这前这快是已经说过的) if v.Kind() == reflect.Ptr && !v.Elem().CanSet() { //reflect.Ptr对应为指针类型;v.Elem().CanSet()取得对应地址下的内容并查看其是否可以进行修改 fmt.Println("传入的类型有误,请检查!") return } else { v = v.Elem() //将实际对象(包含详情内容)进行赋值 } f := v.FieldByName("Name") f1 := v.FieldByName("Id1") if !f.IsValid() { //判断通过名称获取得到到内容是否为空值 fmt.Println("没有Name对应属性字段") return } if !f1.IsValid() { fmt.Println("没有Id1对应属性字段") } if f.Kind() == reflect.String { f.SetString("HelloWorld") } }
运行结果:
{1 OK 13} 没有Id1对应属性字段 {1 HelloWorld 13}
示例四:
那么让我们来写一个比较完整的通过反射对方法等动态调用
package main import ( "fmt" "reflect" ) //定义一个用户结构体 type User struct { Id int Name string Age int } //为User绑定方法 func (u User) HelloDisplay(name string) { fmt.Println("Hello", name, " my name is ", u.Name) } func main() { u := User{1, "OK", 29} u.HelloDisplay("jack") //正常调用 /** 以下方式为反射调用,最优到代码写法就是新写一个方法且在开始是通过kind判断类型是否正确且需要判断有没有对应方法等 */ v := reflect.ValueOf(u) //通过反射得到类型内容 methodV := v.MethodByName("HelloDisplay") //通过方法名称得道方法实体 args := []reflect.Value{reflect.ValueOf("jack")} //设置反射传入的参数 methodV.Call(args) }
运行结果:
Hello jack my name is OK Hello jack my name is OK