• Golang反射下篇


    本文是Golang反射-上篇的续篇内容,主要介绍反射实际的一些使用

    1、判断类型interface.Type

    利用类型断言来判断数据类型的用法如下

    package main
    
    import "fmt"
    
    func main()  {
    	var s interface{} = "abc"
    	switch s.(type) {
    	case string:
    		fmt.Println("s.type=string")
    	case int:
    		fmt.Println("s.type=int")
    	case bool:
    		fmt.Println("s.type=bool")
    	default:
    		fmt.Println("未知的类型")
    	}
    }
    

    上述类型判断的问题

    • 类型判断会写很多,代码很长
    • 类型还会增删,不灵活

    如果使用反射获取变量内部的信息

    • reflect包提供ValueOf和TypeOf
    • reflect.ValueOf:获取输入接口中数据的值,如果为空返回0
    • reflect.TypeOf:获取输入接口中值的类型,如果为空返回nil
    • TypeOf能传入所有类型,是因为所有的类型都实现了空接口
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main()  {
    	var s interface{} = "abc"
    	//TypeOf会返回目标的对象
    	reflectType:=reflect.TypeOf(s)
    	reflectValue:=reflect.ValueOf(s)
    	fmt.Printf("[typeof:%v]\n", reflectType)  // string
    	fmt.Printf("[valueof:%v]\n", reflectValue)  // abc
    }
    

    2、自定义struct的反射

    自定义struct的相关操作

    • 对于成员变量

      • 先获取interface的reflect.Type,然后遍历NumField
      • 再通过reflect.Type的Field获取字段名及类型
      • 最后通过Field的interface获取对应的value
    • 对于方法

      • 先获取interface的reflect.Type,然后遍历NumMethod
      • 再通过reflect.Type的t.Method获取真实的方法名
      • 最后通过Name和Type获取方法的类型和值

    注意点

    • 用于对未知类型进行遍历探测其Field,抽象成一个函数
    • go语言里面struct成员变量小写,在反射的时候直接panic()
    • 结构体方法名小写是不会panic的,反射值也不会被查看到
    • 指针方法是不能被反射查看到的
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	Name string
    	Age  int
    }
    
    type Student struct {
    	Person     // 匿名结构体嵌套
    	StudentId  int
    	SchoolName string
    	Graduated  bool
    	Hobbies    []string
    	//panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
    	//hobbies    []string
    	Label      map[string]string
    }
    
    func (s *Student) GoHome() {
    	fmt.Printf("回家了,sid:%d\n", s.StudentId)
    }
    
    //func (s Student) GoHome() {
    //	fmt.Printf("回家了,sid:%d\n", s.StudentId)
    //}
    
    func (s Student) GotoSchool() {
    	fmt.Printf("上学了,sid:%d\n", s.StudentId)
    }
    
    func (s *Student) graduated() {
    	fmt.Printf("毕业了,sid:%d\n", s.StudentId)
    }
    
    //func (s Student) Ggraduated() {
    //	fmt.Printf("毕业了,sid:%d\n", s.StudentId)
    //}
    
    func reflectProbeStruct(s interface{}) {
    	// 获取目标对象
    	t := reflect.TypeOf(s)
    	fmt.Printf("对象的类型名称 %s\n", t.Name())
    	// 获取目标对象的值类型
    	v := reflect.ValueOf(s)
    	// 遍历获取成员变量
    	for i := 0; i < t.NumField(); i++ {
    		// Field 代表对象的字段名
    		key := t.Field(i)
    		value := v.Field(i).Interface()
    		// 字段
    		if key.Anonymous {
    			fmt.Printf("匿名字段 第 %d 个字段,字段名 %s, 字段类型 %v, 字段的值 %v\n", i+1, key.Name, key.Type, value)
    		} else {
    			fmt.Printf("命名字段 第 %d 个字段,字段名 %s, 字段类型 %v, 字段的值 %v\n", i+1, key.Name, key.Type, value)
    		}
    	}
    	// 打印方法
    	for i := 0; i < t.NumMethod(); i++ {
    		m := t.Method(i)
    		fmt.Printf("第 %d 个方法,方法名 %s, 方法类型 %v\n", i+1, m.Name, m.Type)
    	}
    }
    
    func main() {
    	s := Student{
    		Person: Person{
    			"geek",
    			24,
    		},
    		StudentId:  123,
    		SchoolName: "Beijing University",
    		Graduated:  true,
    		Hobbies:    []string{"唱", "跳", "Rap"},
    		//hobbies:    []string{"唱", "跳", "Rap"},
    		Label:      map[string]string{"k1": "v1", "k2": "v2"},
    	}
    	p := Person{
    		Name: "张三",
    		Age:  100,
    	}
    	reflectProbeStruct(s)
    	reflectProbeStruct(p)
    	/*
    	对象的类型名称 Student
    	匿名字段 第 1 个字段,字段名 Person, 字段类型 main.Person, 字段的值 {geek 24}
    	命名字段 第 2 个字段,字段名 StudentId, 字段类型 int, 字段的值 123
    	命名字段 第 3 个字段,字段名 SchoolName, 字段类型 string, 字段的值 Beijing University
    	命名字段 第 4 个字段,字段名 Graduated, 字段类型 bool, 字段的值 true
    	命名字段 第 5 个字段,字段名 Hobbies, 字段类型 []string, 字段的值 [唱 跳 Rap]
    	命名字段 第 6 个字段,字段名 Label, 字段类型 map[string]string, 字段的值 map[k1:v1 k2:v2]
    	第 1 个方法,方法名 GotoSchool, 方法类型 func(main.Student)
    	对象的类型名称 Person
    	命名字段 第 1 个字段,字段名 Name, 字段类型 string, 字段的值 张三
    	命名字段 第 2 个字段,字段名 Age, 字段类型 int, 字段的值 100
    	 */
    }
    

    3、结构体标签和反射

    • json的标签解析出json
    • yaml的标签解析出yaml
    • xorm、gorm的标签标识数据库db字段
    • 自定义标签
    • 原理是t.Field.Tag.Lookup("标签名")

    示例

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"gopkg.in/yaml.v2"
    	"io/ioutil"
    )
    
    type Person struct {
    	Name string `json:"name" yaml:"yaml_name"`
    	Age  int    `json:"age" yaml:"yaml_age"`
    	City string `json:"city" yaml:"yaml_city"`
    	//City string `json:"-" yaml:"yaml_city"` // 忽略json:"-"
    }
    
    // json解析
    func jsonWork() {
    	// 对象Marshal成字符串
    	p := Person{
    		Name: "geek",
    		Age:  24,
    		City: "Beijing",
    	}
    	data, err := json.Marshal(p)
    	if err != nil {
    		fmt.Printf("json.marshal.err: %v\n", err)
    	}
    	fmt.Printf("person.marshal.res: %v\n", string(data))
    
    	// 从字符串解析成结构体
    	p2str := `{
    	"name": "张三",
    	"age": 38,
    	"city": "山东"
    	}`
    	var p2 Person
    	err = json.Unmarshal([]byte(p2str), &p2)
    	if err != nil {
    		fmt.Printf("json.unmarshal.err: %v\n", err)
    		return
    	}
    	fmt.Printf("person.unmarshal.res: %v\n", p2)
    }
    
    // yaml解析
    func yamlWork() {
    	filename := "a.yaml"
    	content, err := ioutil.ReadFile(filename)
    	if err != nil {
    		fmt.Printf("ioutil.ReadFile.err: %v\n", err)
    		return
    	}
    	p := &Person{}
    	//err = yaml.Unmarshal([]byte(content), p)
    	err = yaml.UnmarshalStrict([]byte(content), p)  // 解析严格,考虑多余字段,忽略字段等
    	if err != nil {
    		fmt.Printf("yaml.UnmarshalStrict.err: %v\n", err)
    		return
    	}
    	fmt.Printf("yaml.UnmarshalStrict.res: %v\n", p)
    }
    
    func main() {
    	jsonWork()
    	/*
    		person.marshal.res: {"name":"geek","age":24,"city":"Beijing"}
    		person.unmarshal.res: {张三 38 山东}
    	*/
    	yamlWork()
    	/*
    		yaml.UnmarshalStrict.res: &{李四 18 Shanghai}
    	 */
    }
    

    解析的yaml内容

    yaml_name: 李四
    yaml_age: 18
    yaml_city: Shanghai
    
    • 自定义标签格式解析
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	Name string `aa:"name"`
    	Age  int    `aa:"age"`
    	City string `aa:"city"`
    }
    
    // CustomParse 自定义解析
    func CustomParse(s interface{}) {
    	// TypeOf type类型
    	r:=reflect.TypeOf(s)
    	value := reflect.ValueOf(s)
    	for i:=0;i<r.NumField();i++{
    		field:=r.Field(i)
    		key:=field.Name
    		if tag, ok:=field.Tag.Lookup("aa");ok{
    			if tag == "-"{
    				continue
    			}
    			fmt.Printf("找到了aa标签, key: %v, value: %v, tag: %s\n", key, value.Field(i), tag)
    		}
    	}
    }
    
    func main() {
    	p := Person{
    		Name: "geek",
    		Age:  24,
    		City: "Beijing",
    	}
    	CustomParse(p)
    	/*
    	找到了aa标签, key: Name, value: geek, tag: name
    	找到了aa标签, key: Age, value: 24, tag: age
    	找到了aa标签, key: City, value: Beijing, tag: city
    	 */
    }
    

    4、反射调用函数

    valueFunc := reflect.ValueOf(Add) //函数也是一种数据类型
    typeFunc := reflect.TypeOf(Add)
    argNum := typeFunc.NumIn()            //函数输入参数的个数
    args := make([]reflect.Value, argNum) //准备函数的输入参数
    for i := 0; i < argNum; i++ {
    	if typeFunc.In(i).Kind() == reflect.Int {
    		args[i] = reflect.ValueOf(3) //给每一个参数都赋3
    	}
    }
    sumValue := valueFunc.Call(args) //返回[]reflect.Value,因为go语言的函数返回可能是一个列表
    if typeFunc.Out(0).Kind() == reflect.Int {
    	sum := sumValue[0].Interface().(int) //从Value转为原始数据类型
    	fmt.Printf("sum=%d\n", sum)
    }
    

    5、反射调用方法

    示例

    user := User{
    	Id:     7,
    	Name:   "杰克逊",
    	Weight: 65.5,
    	Height: 1.68,
    }
    valueUser := reflect.ValueOf(&user)              //必须传指针,因为BMI()在定义的时候它是指针的方法
    bmiMethod := valueUser.MethodByName("BMI")       //MethodByName()通过Name返回类的成员变量
    resultValue := bmiMethod.Call([]reflect.Value{}) //无参数时传一个空的切片
    result := resultValue[0].Interface().(float32)
    fmt.Printf("bmi=%.2f\n", result)
    
    //Think()在定义的时候用的不是指针,valueUser可以用指针也可以不用指针
    thinkMethod := valueUser.MethodByName("Think")
    thinkMethod.Call([]reflect.Value{})
    
    valueUser2 := reflect.ValueOf(user)
    thinkMethod = valueUser2.MethodByName("Think")
    thinkMethod.Call([]reflect.Value{})
    

    过程

    • 首先通过reflect.ValueOf(p1) 获取得到反射类型对象
    • reflect.ValueOf(p1).MethodByName需 要传入准确的方法名称(名称不对会panic: reflect: call of reflect.Value.Call on zero Value),MethodByName代表注册
    • []reflect.Value 这是最终需要调用方法的参数,无参数传空切片
    • call调用
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	Name   string
    	Age    int
    	Gender string
    }
    
    func (p Person) ReflectCallFuncWithArgs(name string, age int) {
    	fmt.Printf("调用的是带参数的方法, args.name: %s, args.age: %d, p.name: %s, p.age: %d\n",
    		name,
    		age,
    		p.Name,
    		p.Age,
    	)
    }
    
    func (p Person) ReflectCallFuncWithNoArgs() {
    	fmt.Printf("调用的是不带参数的方法\n")
    }
    
    func main() {
    	p1 := Person{
    		Name:   "geek",
    		Age:    24,
    		Gender: "男",
    	}
    	// 1.首先通过reflect.ValueOf(p1)获取得到反射值类型
    	getValue := reflect.ValueOf(p1)
    	// 2.带参数的方法调用
    	methodValue1 := getValue.MethodByName("ReflectCallFuncWithArgs")
    	// 参数是reflect.Value的切片
    	args1 := []reflect.Value{reflect.ValueOf("张三"), reflect.ValueOf(30)}
    	methodValue1.Call(args1)
    	// 3.不带参数的方法调用
    	methodValue2 := getValue.MethodByName("ReflectCallFuncWithNoArgs")
    	// 参数是reflect.Value的切片
    	args2 := make([]reflect.Value, 0)
    	methodValue2.Call(args2)
    	/*
    	调用的是带参数的方法, args.name: 张三, args.age: 30, p.name: geek, p.age: 24
    	调用的是不带参数的方法
    	 */
    }
    

    6、反射创建值

    6.1 反射创建struct

    t := reflect.TypeOf(User{})
    value := reflect.New(t) //根据reflect.Type创建一个对象,得到该对象的指针,再根据指针提到reflect.Value
    value.Elem().FieldByName("Id").SetInt(10)
    value.Elem().FieldByName("Name").SetString("宋江")
    value.Elem().FieldByName("Weight").SetFloat(78.)
    value.Elem().FieldByName("Height").SetFloat(168.4)
    user := value.Interface().(*User) //把反射类型转成go原始数据类型
    fmt.Printf("id=%d name=%s weight=%.1f height=%.1f\n", user.Id, user.Name, user.Weight, user.Height)
    

    6.2 反射创建slice

    var slice []User
    sliceType := reflect.TypeOf(slice)
    sliceValue := reflect.MakeSlice(sliceType, 1, 3) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc
    sliceValue.Index(0).Set(reflect.ValueOf(User{
    	Id:     8,
    	Name:   "李达",
    	Weight: 80,
    	Height: 180,
    }))
    users := sliceValue.Interface().([]User)
    fmt.Printf("1st user name %s\n", users[0].Name)
    

    6.3 反射创建map

    var userMap map[int]*User
    mapType := reflect.TypeOf(userMap)
    // mapValue:=reflect.MakeMap(mapType)
    mapValue := reflect.MakeMapWithSize(mapType, 10) //reflect.MakeMap、reflect.MakeSlice、reflect.MakeChan、reflect.MakeFunc
    
    user := &common.User{
    	Id:     7,
    	Name:   "杰克逊",
    	Weight: 65.5,
    	Height: 1.68,
    }
    key := reflect.ValueOf(user.Id)
    mapValue.SetMapIndex(key, reflect.ValueOf(user))                    //SetMapIndex 往map里添加一个key-value对
    mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的map
    userMap = mapValue.Interface().(map[int]*User)
    fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)
    

    7、反射修改值

    反射修改值要求必须是指针类型

    修改值的操作:pointer.Elem().Setxxx()

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	var num float64 = 3.14
    	fmt.Printf("原始值 %f\n", num)
    	// 通过reflect.ValueOf获取num中的value,必须是指针才可以修改值
    	//pointer := reflect.ValueOf(num)  // 直接传值会panic
    	pointer := reflect.ValueOf(&num)
    	newValue := pointer.Elem()
    	// 赋新的值
    	newValue.SetFloat(5.66)
    	fmt.Printf("新的值 %f\n", num)
    }
    

    7.1 反射修改struct

    user := User{
    	Id:     7,
    	Name:   "杰克逊",
    	Weight: 65.5,
    	Height: 1.68,
    }
    valueUser := reflect.ValueOf(&user)
    // valueS.Elem().SetInt(8)//会panic
    valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通过Name返回类的成员变量。不能在指针Value上调用FieldByName
    addrValue := valueUser.Elem().FieldByName("addr")
    if addrValue.CanSet() {
    	addrValue.SetString("北京")
    } else {
    	fmt.Println("addr是未导出成员,不可Set") //以小写字母开头的成员相当于是私有成员
    }
    

    7.2 反射修改slice

    下面示例,间接的实现了append功能

    users := make([]*User, 1, 5) //len=1,cap=5
    
    sliceValue := reflect.ValueOf(&users) //准备通过Value修改users,所以传users的地址
    if sliceValue.Elem().Len() > 0 {      //取得slice的长度
    	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("哈哈哈")
    	// u0 := users[0]
    	fmt.Printf("1st user name change to %s\n", users[0].Name)
    }
    
    sliceValue.Elem().SetCap(3) //新的cap必须位于原始的len到cap之间
    sliceValue.Elem().SetLen(2)
    //调用reflect.Value的Set()函数修改其底层指向的原始数据
    sliceValue.Elem().Index(1).Set(reflect.ValueOf(&User{
    	Id:     8,
    	Name:   "geek",
    	Weight: 80,
    	Height: 180,
    }))
    fmt.Printf("2nd user name %s\n", users[1].Name)
    

    7.3 反射修改map

    u1 := &User{
    	Id:     7,
    	Name:   "杰克逊",
    	Weight: 65.5,
    	Height: 1.68,
    }
    u2 := &User{
    	Id:     8,
    	Name:   "杰克逊",
    	Weight: 65.5,
    	Height: 1.68,
    }
    userMap := make(map[int]*User, 5)
    userMap[u1.Id] = u1
    
    mapValue := reflect.ValueOf(&userMap)                                                         //准备通过Value修改userMap,所以传userMap的地址
    mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2))                      //SetMapIndex 往map里添加一个key-value对
    mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根据Key取出对应的map
    for k, user := range userMap {
    	fmt.Printf("key %d name %s\n", k, user.Name)
    }
    

    See you ~

    关注公众号加群,更多原创干货与你分享 ~

  • 相关阅读:
    简单二分求解(木板补漏问题)
    switch语句和for循环
    JAVA运算符和优先级
    JAVA数据类型
    JAVA基础
    JAVA开发环境
    linux配置IP地址
    LVM逻辑分区
    用户和用户组
    第一天
  • 原文地址:https://www.cnblogs.com/ssgeek/p/15596157.html
Copyright © 2020-2023  润新知