Go语言基础之21--反射
一、变量介绍
1.1 变量的内在机制
A. 类型信息,这部分是元信息,是预先定义好的;比如:string、int等
B. 值类型,这部分是程序运行过程中,动态改变的;比如:是变量实例所存储的真是的值。
例1:
例2:
二、反射介绍
2.1 反射与空接口
A. 空接口可以存储任何类型的变量
B. 那么给你一个空接口,如何知道里面存储的是什么东西?此时我们就需要借助反射。
C. 在运行时动态的获取一个变量的类型信息和值信息,就叫反射。
2.2 如何分析?
针对一个空接口,如何知道里面存储的是什么东西?此时我们就需要借助反射。
如何分析呢?
A. 内置包 reflect
B. 获取类型信息: reflect.TypeOf
C. 获取值信息: reflect.ValueOf
以TestInterface函数为例:
func TestInterface(a interface{}) {
}
其要传递的值类型是空接口,我们要借助反射做的是概括为以下3点:
1、获取a的类型;
2、动态改变a里面存储的值;
3、如果a里面存储的是一个结构体,那可以通过反射调用结构体里面的方法;
2.3 基本数据类型分析
实例:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) //reflect.TypeOf获取类型信息 }
执行结果:
2.4 Type.Kind(),获取变量的类型
实例:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 t := reflect.TypeOf(x) fmt.Println("type:", t.Kind()) }
执行结果:
kind函数在go源码底层提供判断可选的类型如下:
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 )
2.5 reflect.ValueOf,获取变量的值相关信息
ValueOf获取的是变量实例的所有信息(包括类型、值等等)
实例:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 v := reflect.ValueOf(x) //和reflect.TypeOf功能是一样的 fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float()) }
执行结果:
2.6 通过反射设置变量的值
实例:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) v.SetFloat(6.8) fmt.Println("value:", v.Float()) }
执行结果:
我们此处修改变量的值,变量是值类型,修改是不生效的,存在这个问题,反射是不允许执行的,会直接panic的,那么我们就此进行改进,改进实例如下:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 //传地址进去,不传地址的话,改变的是副本的值 //所以在reflect包里直接崩溃了!!!!! v := reflect.ValueOf(&x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) v.SetFloat(6.8) fmt.Println("value:", v.Float()) }
执行结果如下:
可以发现虽然直接传入指针,但是依然有问题,因为我们传递的是地址(指针),而我们要改变的是这个地址对应的值,下面我们就这个问题在进行改进,改进实例如下:
package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 //传地址进去,不传地址的话,改变的是副本的值 //所以在reflect包里直接崩溃了!!!!! v := reflect.ValueOf(&x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("kind is point:", v.Kind() == reflect.Ptr) //通过Elem()获取指针指向的变量,从而完成赋值操作。 //正常操作是通过*号来解决的,比如 //var *p int = new(int) //*p = 100 v.Elem().SetFloat(6.8) fmt.Println("value:", x) }
执行结果:
可以发现彻底解决了
2.7 补充:完整例子
实例如下:
package main import ( "fmt" "reflect" ) func TestType(a interface{}) { t := reflect.TypeOf(a) //获取a变量存的类型信息 fmt.Printf("t = %v\n", t) kind := t.Kind() switch kind { case reflect.Int: fmt.Printf("a is int\n") case reflect.String: fmt.Printf("a is string\n") } } func TestValue(a interface{}) { v := reflect.ValueOf(a) //ValueOf函数返回的是一个value结构体,通过value就可以操作a,现在都赋值给v,所以我们就可以通过v去间接操作a,v也就相当于拿到了a的所有信息 //注意v.Type() 和 reflect.TypeOf()的功能一样 //动态改变a的值 t := v.Type() switch t.Kind() { //获取变量的类型 case reflect.Int: v.SetInt(1000) case reflect.String: v.SetString("xxxxxxx") case reflect.Ptr: t1 := v.Elem().Type() //v.Elem就相当于在变量前加个*号,获取该地址对应的值,此处就是获取指针变量对应的真实信息 switch t1.Kind() { case reflect.Int: v.Elem().SetInt(1000) fmt.Printf("ptr is int\n") case reflect.String: v.Elem().SetString("xxxxxxx") fmt.Printf("ptr is string\n") } fmt.Printf("a is point type\n") } } func main() { var a int TestType(a) fmt.Printf("a=%v\n", a) var b string TestType(b) TestValue(&a) TestValue(&b) fmt.Printf("a=%v b=%v", a, b) }
执行结果:
三、结构体反射
最常见的还是结构体反射,我们该如何通过反射去操作一个结构体(可以通过反射去获取结构体中的字段信息以及调用结构体里面的方法)
3.1 获取结构体类型相关信息
实例:
package main import ( "fmt" "reflect" ) type S struct { A int B string } func main() { s := S{23, "skidoo"} v := reflect.ValueOf(s) t := v.Type() for i := 0; i < v.NumField(); i++ { f := v.Field(i) fmt.Printf("%d: %s %s = %v\n", i, t.Field(i).Name, f.Type(), f.Interface()) } }
执行结果:
3.2 设置结构体相关字段的值
实例:
package main import ( "fmt" "reflect" ) type S struct { A int B string } func main() { s := S{23, "skidoo"} v := reflect.ValueOf(&s) t := v.Type() v.Elem().Field(0).SetInt(100) for i := 0; i < v.Elem().NumField(); i++ { f := v.Elem().Field(i) fmt.Printf("%d: %s %s = %v\n", i, t.Elem().Field(i).Name, f.Type(), f.Interface()) } }
执行结果:
3.3 获取结构体的方法信息
实例:
package main import ( "fmt" "reflect" ) type S struct { A int B string } func (s *S) Test() { fmt.Println("this is a test") } func main() { s := S{23, "skidoo"} v := reflect.ValueOf(&s) t := v.Type() //获取类型信息 v.Elem().Field(0).SetInt(100) //将第0个字段设置成100 fmt.Println("method num:", v.NumMethod()) //NumMethod获取方法的数量 for i := 0; i < v.NumMethod(); i++ { f := t.Method(i) //将获取到的第i个方法的类型信息存到f fmt.Printf("%d method, name:%v, type:%v\n", i, f.Name, f.Type) } }
3.4 调用结构体中的方法
实例1:
要调用结构体中的方法,就需要先用ValueOf获取实例的信息,类型只是元信息,
实例:
package main import ( "fmt" "reflect" ) type S struct { A int B string } func (s *S) Test() { //Test方法没有参数 fmt.Println("this is a test") } func (s *S) SetA(a int) { s.A = a } func main() { s := S{23, "skidoo"} v := reflect.ValueOf(&s) m := v.MethodByName("Test") //m就是Test方法的实例,通过MethodByname方法来获取,使用该前提是你要知道结构体的方法是什么 var args1 []reflect.Value //有参数是通过切片传进去,没有参数,就是一个空切片 m.Call(args1) //借助call方法进行调用 setA := v.MethodByName("SetA") var args2 []reflect.Value args2 = append(args2, reflect.ValueOf(100)) //往切片中追加一个int 100 setA.Call(args2) fmt.Printf("s:%#v\n", s) }
执行结果:
实例2:
package main import ( "fmt" "reflect" ) type User struct { Name string `json:"name"` Age int Sex string } func (u *User) Print() { fmt.Printf("name:%s age:%d sex:%s\n", u.Name, u.Age, u.Sex) } func (u *User) SetName(name string) { u.Name = name } func TestValue(a interface{}) { //调用没有参数的方法 v := reflect.ValueOf(a) methodNum := v.NumMethod() fmt.Printf("method:%v\n", methodNum) m := v.MethodByName("Print") var args []reflect.Value m.Call(args) //调用有参数的方法 v = reflect.ValueOf(a) m = v.MethodByName("SetName") args = args[0:0] args = append(args, reflect.ValueOf("hello world")) m.Call(args) } func main() { var user User user.Name = "xxx" user.Age = 100 user.Sex = "man" TestValue(&user) fmt.Printf("user:%#v\n", user) }
执行结果:
3.5 获取结构体中tag信息
实例:
package main import ( "fmt" "reflect" ) type S struct { F string `species:"gopher" color:"blue" json:"f"` } func main() { s := S{} st := reflect.TypeOf(s) field := st.Field(0) fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"), field.Tag.Get("json")) }
执行结果:
3.6 小例子
package main import ( "fmt" "reflect" ) type User struct { Name string `json:"name"` Age int Sex string } //1. 获取a的类型 //2. 我要动态改变a里面存的值 //3. 如果a里面存储的是一个结构体,那可以通过反射获取结构体中的字段信息以及调用结构体里面的方法 func TestValue(a interface{}) { v := reflect.ValueOf(a) t := v.Type() switch t.Kind() { //获取变量的类型 case reflect.Struct: //假定变量是Struct结构体 fieldNum := t.NumField() //通过NumField拿到结构体中的字段数量 fmt.Printf("field num:%d\n", fieldNum) for i := 0; i < fieldNum; i++ { field := t.Field(i) //字段的类型信息 vField := v.Field(i) //变量的实例的值的相关信息 fmt.Printf("field[%d] name:%s, json key:%s, val:%v\n", i, field.Name, field.Tag.Get("json"), vField.Interface()) //因为这里不确定值的类型,所以通过Interface()自动帮我们判别 } } } func main() { var user User user.Name = "harden" user.Age = 100 user.Sex = "man" TestValue(user) fmt.Printf("user:%#v\n", user) }
执行结果:
四、案例:序列化
下面我们借助反射写了一个json序列化的包
目录结构如下:
实例如下:
json.go:
package reflect_json import ( "fmt" "reflect" ) func Marshal(data interface{}) (jsonStr string) { //data就是用户传进来的数据信息,返回的是序列化完的json序列串 t := reflect.TypeOf(data) //获取类型信息 v := reflect.ValueOf(data) switch t.Kind() { //猜是什么类型 case reflect.String, reflect.Int, reflect.Int32: //简单数据类型处理几乎是一致的 jsonStr = fmt.Sprintf("\"%v\"", data) // \"表示双引号 case reflect.Struct: //结构体 numField := t.NumField() //获取结构体字段数量 for i := 0; i < numField; i++ { //类型信息 name := t.Field(i).Name //获取字段名字 tag := t.Field(i).Tag.Get("json") //获取有tag的字段名(此处针对json) if len(tag) > 0 { //有tag,优先使用tag name = tag } //值信息 vField := v.Field(i) //返回值是一个Value的结构体 vFieldValue := vField.Interface() //想要获取字段的值,用interface即可 //拼接json if t.Field(i).Type.Kind() == reflect.String { jsonStr += fmt.Sprintf("\"%s\":\"%v\"", name, vFieldValue) //如果是字符串就加双引号 } else { jsonStr += fmt.Sprintf("\"%s\":%v", name, vFieldValue) //不是字符串值不用加双引号 } //json串的话,字段与字段之间还有1个单引号,最后一个字段没有逗号 if i != numField-1 { //如果不是最后一个,就加一个逗号 jsonStr += "," } } jsonStr = "{" + jsonStr + "}" //最后在最前面和最后面加一个大括号 } return }
接下来我们通过一个小例子验证一下我们开发的这个包是否好用
实例如下:
main.go:
package main import ( "fmt" "9/after_class/reflect_json" ) /* { "name": "xxx", "Age":100, "Sex": "xx" } */ type User struct { Name string `json:"name"` Age int Sex string } func main() { var a string = "hello world" jsonStr := reflect_json.Marshal(a) fmt.Printf(jsonStr) var user User user.Age = 10900 user.Name = "user01" user.Sex = "man" jsonStr = reflect_json.Marshal(user) fmt.Printf("user marshal:%s\n", jsonStr) }
执行结果如下:
五、反射总结以及应用场景
5.1 反射总结
A. 在运行时动态的获取一个变量的类型信息和值信息
5.2 应用场景
A. 序列化和反序列化,比如json, protobuf等各种数据协议
B. 各种数据库的ORM, 比如gorm, sqlx等数据库中间件