目录
一、反射编程
1、reflect
2、反射的特点
3、kind
4、获取反射回来的类型
5、利用反射编写灵活的程序
a、按名字访问结构的成员
b、按名字访问结构体的方法
c、Elem()
6、Struct Tag
a、struct tag 的格式
b、用途
二、万能程序
1、DeepEqual
a、用 DeepEqual() 比较 map
b、用 DeepEqual() 比较 slice
3、用反射实现万能程序
三、总结
反射最大的用途可能就是通过字符串或者以字符的形式来调用类型中的某一个方法,或者通过传入变量或者方法的名字访问某一个成员。
反射类型:reflect.TypeOf()
反射值:reflect.Value()
当我们需要对反射回来的类型做判断时,Go 语言内置了一个枚举,可以通过 kind() 来返回这个枚举值。
const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 //... )
package reflect_test import ( "fmt" "reflect" "testing" ) //检查反射类型 //用空接口接收任意类型 func CheckType(v interface{}) { t := reflect.TypeOf(v) switch t.Kind() { case reflect.Int, reflect.Int32, reflect.Int64: fmt.Println("Int") case reflect.Float32, reflect.Float64: fmt.Println("Float") default: fmt.Println("unknown type") } } func TestBasicType(t *testing.T) { var f float32 = 1.23 CheckType(f) } /* === RUN TestBasicType Float --- PASS: TestBasicType (0.00s) PASS */
reflect.TypeOf() 和 reflect.ValueOf() 都有 FieldByName()方法。
//s必须是一个 struct 类型 //reflect.ValueOf()只会返回一个值 reflect.ValueOf(s).FieldByName("Name") //reflect.TypeOf()可以返回两个值,第二个值可以用来判断这个值有没有; reflect.TypeOf(s).FieldByName("Name")
FieldByName() 方法返回的是一个 StructField 类型的值。
//FieldByName returns the struct field with the given name //and a boolean indicating if the field was found. FieldByName(name string) (StructField, bool)
FieldByName() 方法调用者必须是一个 struct,而不是指针,源码如下:
//访问 MethodByName() 必须是指针类型 reflect.ValueOf(&s).MethodByName("method_name").Call([]reflect.Value{reflect.ValueOf("new_value")})
package reflect_test import ( "fmt" "reflect" "testing" ) type Employee struct { EmployeeId int //注意后面的 struct tag 的写法,详情见第5点讲解 Name string `format:"normal"` Age int } //更新名字,注意这里的 e 是指针类型 func (e *Employee) UpdateName (newVal string) { e.Name = newVal } //通过反射调用结构体的方法 func TestInvokeByName(t *testing.T) { e := Employee{1, "Jane", 18} //reflect.TypeOf()可以返回两个值,,第二个值可以用来判断这个值有没有; //儿reflect.ValueOf()只会返回一个值 if nameField, ok := reflect.TypeOf(e).FieldByName("Name"); !ok { t.Error("Failed to get 'Name' field") } else { //获取反射取到的字段的 tag 的值 t.Log("Tag:Format", nameField.Tag.Get("format")) } reflect.ValueOf(&e).MethodByName("UpdateName").Call([]reflect.Value{reflect.ValueOf("Mike")}) t.Log("After update name: ", e) } /* === RUN TestInvokeByName reflect_test.go:28: Tag:Format normal reflect_test.go:33: After update name: {1 Mike 18} --- PASS: TestInvokeByName (0.00s) PASS */
因为 FieldByName() 必须要结构才能调用,如果参数是一个指向结构体的指针,我们需要用到 Elem() 方法,它会帮忙获得指针指向的结构。
//reflect.ValueOf(demoPtr)).Elem() 返回的是字段的值 reflect.ValueOf(demoPtr).Elem() //reflect.ValueOf(st)).Elem().Type() 返回的是字段类型 reflect.ValueOf(demoPtr).Elem().Type() //在指针类型参数调用 FieldByName() 方法。 reflect.ValueOf(demoPtr).Elem().FieldByName("Name") //在指针类型参数调用 FieldByName() 方法。 reflect.ValueOf(demoPtr).Elem().Type().FieldByName("Name")
结构体里面可以对某些字段做特殊的标记,它是一个 key, value的格式。
type Demo struct { //先用这个符号(``)包起来,然后写上 key,value的格式 Name string `format:"normal"` }
Go 内置的 Json 解析会用到 tag 来做一些标记。
根据我前面的文章(Go Map)和(Go数组和切片),我们都知道两个map类型之间是不能互相比较的,两个slice类型之间也不能进行比较,但是有没有什么办法能让他们可以进行比较呢?发射里面的 DeepEqual() 可以帮我们实现这个功能。
package flexible_reflect import ( "reflect" "testing" ) //用 DeepEqual() 比较两个 map 类型 func TestMapComparing(t *testing.T) { m1 := map[int]string{1:"one", 2:"two", 3:"three"} m2 := map[int]string{1:"one", 2:"two", 3:"three"} if reflect.DeepEqual(m1, m2) { t.Log("yes") } else { t.Log("no") } } /* === RUN TestMapComparing reflect_test.go:77: yes --- PASS: TestMapComparing (0.00s) PASS */
package flexible_reflect import ( "reflect" "testing" ) //用 DeepEqual() 比较两个切片类型 func TestSliceComparing(t *testing.T) { s1 := []int{1, 2, 3, 4} s2 := []int{1, 2, 3, 4} if reflect.DeepEqual(s1, s2) { t.Log("yes") } else { t.Log("no") } } /* === RUN TestSliceComparing flexible_reflect_test.go:32: yes --- PASS: TestSliceComparing (0.00s) PASS */
场景:我们有 Employee 和 Customer 两个结构体,二者有两个相同的字段(Name 和 Age),我们希望写一个通用的程序,可以同时填充这两个不同的结构体。
package flexible_reflect import ( "errors" "fmt" "reflect" "testing" ) type Employee struct { EmployeeId int Name string Age int } type Customer struct { CustomerId int Name string Age int } //用同一个数据填充不同的结构体 //思路:既然是不同的结构体,那么要想通用,所以参数必须是一个空接口才行。 //因为是空接口,所有我们需要对参数类型写断言 func fillDifferentStructByData(st interface{}, data map[string]interface{}) error { //先判断传过来的类型是不是指针 if reflect.TypeOf(st).Kind() != reflect.Ptr { return errors.New("第一个参数必须传一个指向结构体的指针") } //Elem() 用来获取指针指向的值 //如果参数不是指针,会报 panic 错误 //如果参数值是 nil, 获取的值为 0 if reflect.TypeOf(st).Elem().Kind() != reflect.Struct { return errors.New("第一个参数必须是一个结构体类型") } if data == nil { return errors.New("填充用的数据不能为nil") } var ( field reflect.StructField ok bool ) for key, val := range data { //如果结构体里面没有 key 这个字段,则跳过 //reflect.ValueOf(st)).Elem().Type() 返回的是字段类型 //reflect.ValueOf(st)).Elem().Type() 等价于 reflect.TypeOf(st)).Elem() if field, ok = reflect.TypeOf(st).Elem().FieldByName(key); !ok { continue } //如果字段的类型相同,则用 data 的数据填充这个字段的值 if field.Type == reflect.TypeOf(val) { //reflect.ValueOf(st)).Elem() 返回的是字段的值 reflect.ValueOf(st).Elem().FieldByName(key).Set(reflect.ValueOf(val)) } } return nil } //填充姓名和年龄 func TestFillNameAndAge(t *testing.T) { //声明一个 map,用来存放数据,这些数据将会填充到 Employee 和 Customer 这两个结构体中 data := map[string]interface{}{"Name":"Jane", "Age":18} e := Employee{} if err := fillDifferentStructByData(&e, data); err != nil { t.Fatal(err) } c := Customer{} if err := fillDifferentStructByData(&c, data); err != nil { t.Fatal(err) } fmt.Println(e, "\n", c) } /* === RUN TestFillNameAndAge {0 Jane 18} {0 Jane 18} --- PASS: TestFillNameAndAge (0.00s) PASS */
注:这篇博文是我学习中的总结,如有转载请注明出处:
https://blog.csdn.net/DaiChuanrong/article/details/118712207
上一篇:Go-单元测试
下一篇: