前言
一、什么是反射?
二、reflect包的使用
1.reflect.Type
1.1.获取类型名与底层类型
1.2.获取结构体的字段信息和标签
2.reflect.Value
2.1.获取值的类型与底层类型
2.2.获取结构体字段的值信息
2.3.获取并调用结构体的方法
2.4.修改结构体字段的值
三、练习:json格式数据的序列化与反序列化
3.1.标准库中json包的marshal与unmarshal
3.2.通过reflect包自定义marshal与unmarshal
总结
在前面介绍接口的时候有提过,接口是方法的抽象,接口只注重方法的实现,而不在乎是谁调用的,那么当一个函数传入一个接口时,除了使用类型断言,还有什么方法获取该接口的具体类型信息呢,标准库中的reflect包为我们提供了此功能。
《GO语言圣经》声明:“GO语言提供了一种机制在运行时更新变量和检查它们的值、调用他们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制称为反射。”反射常用于判断一个接口变量的类型信息。
reflect包中有两个十分重要的结构体:reflect.Type与reflect.Value,其中Type保存了一个接口变量的所有类型信息,包括类型名,底层类型,类型的字段等信息,Value则包含了接口变量的值信息,包括值的类型,值的底层类型,值的方法等。下面分别介绍这两种结构体的基本使用,更多使用方法请参考Golang标准库中文文档。
reflect.Type包含了一个接口变量的各种类型信息,我们可以通过reflect.TypeOf()函数获得一个reflect.Type结构体。
type person struct {
Name string `heihei:haha`
age int
sex string
}
func (p person) Info() {
fmt.Printf("name:%s,age:%d,sex:%s\n", p.Name, p.age, p.sex)
}
func (p *person) SetAge(age int) {
p.age = age
}
func (p person) GetAge() int {
return p.age
}
func main(){
var p = person{"lulu", 22, "male"}
getType := reflect.TypeOf(p)
}
/*-----------------reflect.Type--------------------------------*/
/*方法
1.Name(),获取该接口的类型名,可以是自定义的别名
2.Kind(),获取该接口的底层类型,如struct,ptr等
*/
fmt.Printf("type:%v,kind:%v\n", getType.Name(), getType.Kind()) // 输出:type:person,kind:struct
// 获取结构体字段信息,注意,这里获取不到字段对应的值
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i) // 通过index获取字段
fmt.Printf("fieldName:%v,fieldTag:%v\n", field.Name, field.Tag)
}
/*输出
fieldName:Name,fieldTag:heihei:haha
fieldName:age,fieldTag:
fieldName:sex,fieldTag:
*/
reflect.Value包含了接口变量的各种值信息,可以通过reflect.ValueOf()获得reflect.Value对象。
/*------------------reflect.Value-----------------------------*/
/*方法
1.Type()获取值的类型,相当于%T
2.Kind()获取值的底层类型
*/
getValue := reflect.ValueOf(p)
fmt.Printf("type:%v,kind:%v\n", getValue.Type(), getValue.Kind()) //输出:type:main.person,kind:struct
// 获取结构体每个字段的值信息
for i := 0; i < getValue.NumField(); i++ {
value := getValue.Field(i)
fmt.Printf("valueType:%v,valueValue:%v\n", value.Type(), value)
}
/*输出
valueType:string,valueValue:lulu
valueType:int,valueValue:22
valueType:string,valueValue:male
*/
// 获取结构体的方法并调用,值接收者只能获得值接收者的方法,方法必须对外可见,否则找不到
for i := 0; i < getValue.NumMethod(); i++ {
method := getValue.Method(i)
fmt.Printf("methodType:%v,mathodKind:%v\n", method.Type(), method.Kind())
if method.Kind() == reflect.Func {
method.Call(nil)
}
}
/*输出
methodType:func() int,mathodKind:func
methodType:func(),mathodKind:func
name:lulu,age:22,sex:male
*/
// 指针接收者方法的调用
pointer := reflect.ValueOf(&p)
method := pointer.MethodByName("SetAge")
method.Call([]reflect.Value{reflect.ValueOf(18)})
p.Info() //name:lulu,age:18,sex:male,成功修改
// 通过reflect修改值
// 必须传入地址
// 字段必须对外导出,否则会panic
nameFiled := pointer.Elem().FieldByName("Name")
//fmt.Println(nameFiled)
nameFiled.SetString("Allen")
p.Info() //name:Allen,age:18,sex:male
通过reflect包修改接口变量的值时,要注意以下四点:
在进行web开发时,前后端交互数据中有一种重要的数据格式,称为json格式。标准库中的json包为我们提供了marshal与unmarshal方法,marshal用于讲一个结构体对象序列化成一个json字符串,而unmarshal则用于将json字符串中各个key-value对赋值给对应的结构体对象。使用方法如下:
type person struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex"`
IsStudent bool `json:"isStudent"`
}
func main(){
p := person{"LuLu", 18, "male", true}
msg, err := json.Marshal(p)
if err != nil {
fmt.Printf("json marshal failed,err:%v\n", err)
return
}
fmt.Println(string(msg)) // 输出:{"name":"LuLu","age":18,"sex":"male","isStudent":true}
var p2 person
err = json.Unmarshal(msg, &p2)
if err != nil {
fmt.Printf("json unmarshal failed,err:%v\n", err)
return
}
fmt.Println(p2) // 输出:{LuLu 18 male true}
}
本次的练习就是用reflect包的相关操作实现上面的marshal方法与unmarshal方法,该练习假设输入全是正确格式的输入(即无需判断输入是否合法),同时,限定结构体字段类型只有string,int与bool类型三种,有需要可自行扩展。
type person struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex"`
IsStudent bool `json:"isStudent"`
}
// 定义一个结构体来保存需要序列化的和结构体字段信息
type fieldInfo struct {
fieldName string // 保存字段名
fieldType reflect.Kind // 保存字段类型
fieldTag string // 保存字段标签
hasTag bool // 记录该字段是否有标签,有标签的话结果要根据标签来
}
// fieldInfo 工厂函数
func newFieldInfo(fieldName string, fieldType reflect.Kind, fieldTag string, hasTag bool) *fieldInfo {
return &fieldInfo{
fieldName: fieldName,
fieldType: fieldType,
fieldTag: fieldTag,
hasTag: hasTag,
}
}
// 序列化函数
func marShal(v interface{}) (string, error) {
// 对于进来的接口类型,先判断是不是结构体,不是则返回错误
vType := reflect.TypeOf(v) // 获取reflect.Type,其中包含了字段的相关信息
if vType.Kind() != reflect.Struct {
return "", errors.New("Please input a struct\n")
}
vValue := reflect.ValueOf(v) // 获取reflect.Value,其中包含了字段的各种值信息
fields := make([]*fieldInfo, 0, 5) // 定义一个切片来获取结构体的所有字段信息,默认只有五个字段,不够的话会通过append扩容
result := make(map[string]string, 5) // 存放结果
// 遍历获取所有字段
for i := 0; i < vType.NumField(); i++ { // 遍历每一个字段
field := vType.Field(i) // 获取字段
tag := field.Tag.Get("json") //获取标签
if tag != "" { // 如果有json标签,设置hasTag属性为true
info := newFieldInfo(field.Name, field.Type.Kind(), tag, true)
fields = append(fields, info) // 保存当前字段信息
} else { // 如果没有json标签,设置hasTag属性为false
info := newFieldInfo(field.Name, field.Type.Kind(), tag, false)
fields = append(fields, info) //保存当前字段信息
}
}
// 遍历获取字段的值
for _, info := range fields {
value := vValue.FieldByName(info.fieldName) // 通过字段名获取字段Value
switch info.fieldType { // 判断当前字段的类型
case reflect.String:
if info.hasTag { // 如果有tag,在结果中使用tag取代name
result[info.fieldTag] = value.String()
} else { // 没有tag则直接使用字段名name
result[info.fieldName] = value.String()
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if info.hasTag { // 如果有tag,在结果中使用tag取代name
result[info.fieldTag] = strconv.Itoa(int(value.Int()))
} else { // 没有tag则直接使用字段名name
result[info.fieldName] = strconv.Itoa(int(value.Int()))
}
case reflect.Bool:
if info.hasTag { // 如果有tag,在结果中使用tag取代name
result[info.fieldTag] = strconv.FormatBool(value.Bool())
} else { // 没有tag则直接使用字段名name
result[info.fieldName] = strconv.FormatBool(value.Bool())
}
default: // 还可以添加许多类型判断,此处示例只添加三种
return "", errors.New("Invalid type\n")
}
}
resultSlice := make([]string, 0, 5)
for key, value := range result {
// 拼接成json格式字符串
resultSlice = append(resultSlice, fmt.Sprintf("\"%s\":\"%s\"", key, value))
}
return "{" + strings.Join(resultSlice, ",") + "}", nil
}
// 假设输入的字符串是满足json格式的字符串
func dealJsonStr(s string) map[string]string {
result := make(map[string]string)
temp1 := s[1 : len(s)-1] // 去掉json字符串前后"{"与"}"
temp2 := strings.Split(temp1, ",") // 获取每个key-value对
for _, v := range temp2 {
temp := strings.Split(v, ":") // 分割key与value
key := temp[0]
value := temp[1]
result[key[1:len(key)-1]] = value[1 : len(value)-1] // 去掉引号
}
return result
}
// 假设输入的字符串是满足json格式的字符串
func unMarshal(s string, v interface{}) error {
vType := reflect.TypeOf(v) // 获取reflect.Type,其中包含了字段的相关信息
vValue := reflect.ValueOf(v) // 获取reflect.Value,其中包含了字段的各种值信息
if vType.Kind() != reflect.Ptr { // 需要修改结构体,因此必须是指针类型
return errors.New("input must ptr\n")
}
jsonMap := dealJsonStr(s) // 解析json字符串,获取key-value对
// 对每个字段信息开始遍历
for i := 0; i < vType.Elem().NumField(); i++ {
field := vType.Elem().Field(i) // 获取当前字段
tag := field.Tag.Get("json") // 获取当前字段tag
fieldType := field.Type.Kind() // 获取当前字段类型
if value, ok := jsonMap[field.Name]; ok {
switch fieldType { // 根据类型设置字段的值
case reflect.String:
vValue.Elem().FieldByName(field.Name).SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
setValue, err := strconv.Atoi(value)
if err != nil {
return err
}
vValue.Elem().FieldByName(field.Name).SetInt(int64(setValue))
case reflect.Bool:
setValue, err := strconv.ParseBool(value)
if err != nil {
return err
}
vValue.Elem().FieldByName(field.Name).SetBool(setValue)
default:
return errors.New("unmarshal invalid type\n")
}
} else if value1, ok1 := jsonMap[tag]; ok1 {
switch fieldType {
case reflect.String:
vValue.Elem().FieldByName(field.Name).SetString(value1)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
setValue, err := strconv.Atoi(value1)
if err != nil {
return err
}
vValue.Elem().FieldByName(field.Name).SetInt(int64(setValue))
case reflect.Bool:
setValue, err := strconv.ParseBool(value1)
if err != nil {
return err
}
vValue.Elem().FieldByName(field.Name).SetBool(setValue)
default:
return errors.New("unmarshal invalid type\n")
}
} else {
return errors.New("can not parse the struct\n")
}
}
return nil
}
func main() {
p := person{"LuLu", 18, "male", true}
j, err := marShal(p)
if err != nil {
fmt.Printf("marshal failed,err:%v\n", err)
return
}
fmt.Println(j) // 输出:{"name":"LuLu","age":"18","sex":"male","isStudent":"true"}
var p2 person
err = unMarshal(j, &p2)
if err != nil {
fmt.Printf("unmarshal failed,err:%v\n", err)
return
}
fmt.Println(p2) // 输出:{LuLu 18 male true}
}
我们可以通过reflect包动态获取一个接口变量的类型信息与值信息,并调用相关的方法或修改值。但是要注意,reflect包十分复杂,且效率十分低下,比正常代码运行速度慢两个数量级,非必要情况谨慎使用reflect包!