zoukankan      html  css  js  c++  java
  • Golang 反射操作整理

    前言

    反射是什么? 我们平常也是经常用到, 而且这名词都用烂了, 这里就不再详细介绍了.

    简单说, 就是有一个不知道是什么类型的变量, 通过反射可以获取其类型, 并可操作属性和方法.

    反射的用途一般是用作生成工具方法, 比如你需要一个ToString方法, 要将变量转为字符串类型, 如果没有反射, 就需要写: ToStringInt, ToStringBool...等等, 每一个类型都要加一个方法. 而有了反射, 只需要一个ToString方法, 不管是什么类型的变量, 都扔给他就好啦.

    对于PHP这种弱类型的语言来说, 如果要调用变量$a$b方法, 只需要$a->$b()即可. 而对于Golang这种强类型的语言就不能这么随意了. 故下面简单介绍一下Golang中反射的应用.

    希望看完反射的用法之后, 至少以后再看相关代码不至于一脸懵逼. 甚至于需要的时候还能自己手撸一套.

    使用

    Golang中反射的操作定义在包reflect中. 此包中主要包括以下两个对象:

    • reflect.Type 用于获取变量的类型信息
    • reflect.Value 用于对变量的值进行操作

    官方文档地址: reflect.Type reflect.Value

    我们在反射中的使用, 也是基于这两个对象的.

    对于反射的使用来说, 其实我们在平常使用中, 主要也就用到下面这几种操作, 大部分复杂的操作百年难得一用:

    • 变量类型和属性的操作
    • 变量方法的操作

    下面就基于这两种操作进行简单演示.

    变量类型和属性的操作

    获取属性信息

    反射中, 类型信息通过reflect.Type对象获取.

    获取类型

    u := struct {
      name    string
    }{}
    // 获取反射对象信息. 既 reflect.Type 对象
    typeOf := reflect.TypeOf(u)
    fmt.Println(typeOf)
    // 变量的类型名 User
    fmt.Println(typeOf.Name())
    // 获取变量的底层类型. 
    // 基础类型的底层类型就是它本身, 比如 int
    // 而所有的自定义结构体, 底层类型都是 struct
    // 所有的指针, 底层类型都是 ptr
    // golang 的所有底层类型定义在 reflect/type.go 文件中. 
    // 可通过 reflect.Array 常量进行定位
    fmt.Println(typeOf.Kind())
    

    但是, 别高兴的太早, 如果将变量u换成一个指针: u := &User{}. 使用上述方法就拿不到变量的类型了. 因为变量内容存储的是地址, 所以需要对该地址进行取值操作. 反射包中的取值操作方法为: reflect.TypeOf(u).Elem(). 拿到值后, 就都一样啦.

    不止是指针, 包括: Array, Chan, Map, Ptr, Slice等, 其实存储的都是地址, 故都需要进行取值操作.

    注意, 这里的底层类型Kind太有用了, 在通过反射处理的时候, 用户自定义类型太多了根本判断不过来, 但是其底层类型Kind一共就十几个, 相同底层类型的结构就可以使用相同的处理方式了.

    结构体

    type User struct {
      Gender int
    }
    u := struct {
      name    string
      Age     int
      Address struct {
        City    string
        Country string
      } `json:"address"`
      User
    }{}
    
    /* 获取反射对象信息 */
    typeOf := reflect.TypeOf(u)
    // 结构体字段的数量
    fmt.Println(typeOf.NumField())
    // 获取第0个字段的信息, 返回 StructField 对象 (此对象下方有说明)
    // 可拿到字段的 Name 等信息, 包括字段的 Type 对象
    fmt.Println(typeOf.Field(0))
    // 根据变量名称获取字段
    fmt.Println(typeOf.FieldByName("name"))
    // 获取第2个结构提的第0个元素
    fmt.Println(typeOf.FieldByIndex([]int{2, 0}))
    
    /* StructField 字段对象内容 */
    structField := typeOf.Field(2)
    // 字段名
    fmt.Println(structField.Name)
    // 字段的可访问的包名
    // 大写字母打头的公共字段, 都可以访问, 故此值为空
    fmt.Println(structField.PkgPath)
    // reflect.Type 对象
    fmt.Println(structField.Type)
    // 字段的标记字符串, 就是后面跟着的 `` 字符串
    // 返回 StructTag 对象, 下方有说明
    fmt.Println(structField.Tag)
    // 字段在结构体的内存结构中偏移量, 字节
    fmt.Println(structField.Offset)
    // 字段在结构体中的索引
    fmt.Println(structField.Index)
    // 匿名字段. 结构体中的 Gender 就属于匿名字段
    fmt.Println(structField.Anonymous)
    
    /* StructTag 标签内容 */
    tag := structField.Tag
    // 获取指定名称的标签值, 若不存在, 返回空字符串
    fmt.Println(tag.Get("json"))
    // 与 Get 方法区别是, 第二个参数会返回若标签是否存在存在
    // 有些标签的空字符串与未定义行为不同时, 可使用此方法获取
    fmt.Println(tag.Lookup("json"))
    

    数组

    切片的底层类型为Slice, 但不同切片中存储的对象类型是不一样的.

    说白了, 数组其实就是一个指向首地址的指针嘛. 故要想获取数组元素的内容, 做一次取值操作就可以啦.

    l := []int{1, 2}
    
    typeOf := reflect.TypeOf(l)
    // 空, 这里为什么空上面说过了, 数组是一个指针
    fmt.Println(typeOf.Name())
    // slice
    fmt.Println(typeOf.Kind())
    // 获取数组元素的类型
    fmt.Println(typeOf.Elem().Kind())
    fmt.Println(typeOf.Elem().Name())
    

    如果数组中存放的是结构体, 在用作结构体处理就好啦

    map

    m := map[string]int{
      "a": 1,
    }
    typeOf := reflect.TypeOf(m)
    // map 不使用取值也可以打印名字 map[string]int 不懂
    fmt.Println(typeOf.Name())
    // 对象底层类型. map
    fmt.Println(typeOf.Kind())
    // 获取 map 的 key 的类型
    fmt.Println(typeOf.Key().Kind())
    // 获取 map value 的类型
    fmt.Println(typeOf.Elem().Kind())
    

    获取属性值

    反射中, 对于值的操作, 都是通过reflect.Value对象实现的, 此对象通过reflect.ValueOf获取.

    同时, 所有的Value对象都可以调用Interface方法, 来将其转回Interface{}对象, 然后再通过类型断言进行转换.

    基础类型

    基础类型的取值, GO提供了对应的方法, 使用起来也很简单.

    // 基础类型取值
    a := int64(3)
    valueOf := reflect.ValueOf(&a)
    // 取基础类型.
    // 注意, 若不是相关类型, 会报错. 可查看源码
    // 所有的整形, 都返回 int64, 若需要 int32, 可拿到返回值后强转
    fmt.Println(valueOf.Int())
    //fmt.Println(valueOf.Float())
    //fmt.Println(valueOf.Uint())
    // ... 等等
    

    结构体

    如果是自定义的结构体怎么取值呢? 这, 一直找到基础类型. 因为所有的自定义结构体都是有基础类型组成的嘛.

    u := struct {
    Name string
    Age  int
    }{"xiao ming", 20}
    valueOf := reflect.ValueOf(u)
    fmt.Println(valueOf.Field(0).String())
    

    数组

    如果是数组呢? 也很简单

    l := []int{1, 2, 3}
    
    valueOf := reflect.ValueOf(l)
    // 修改指定索引的值
    fmt.Println(valueOf.Elem().Index(0))
    // 获取数组长度
    fmt.Println(valueOf.Elem().Len())
    

    map

    通过反射获取Map的值时, 取到的是Value对象, 同时要使用Value对象进行取值. 毕竟Mapkeyvalue类型都是不固定的嘛.

    m := map[string]string{
    "a": "1",
    }
    valueOf := reflect.ValueOf(m)
    // 获取指定索引的值
    fmt.Println(valueOf.MapIndex(reflect.ValueOf("a")))
    // 若指定索引的值不存在, 会返回一个 kind 为 Invalid 的 Value 对象
    fmt.Println(valueOf.MapIndex(reflect.ValueOf("c")))
    //  取 map 大小
    fmt.Println(valueOf.Len())
    // 获取 map 的所有 key, 返回 Value 对象列表
    fmt.Println(valueOf.MapKeys())
    // 遍历 map 用的迭代器
    mapIter := valueOf.MapRange()
    mapIter.Next() // 将迭代指针直线下一个, 返回是否还有数据
    fmt.Println(mapIter.Value())
    fmt.Println(mapIter.Key())
    

    属性赋值

    基础类型的赋值, reflect.Value对象提供了相关方法, 都以 Set 开头.

    这里注意, 只有指针类型的的变量才能被赋值. 其实很好理解, 值类型在方法调用时是通过复制传值的. 只有传递指针才能够找到原始值的内存地址进行修改.

    故, 我们在赋值之前, 要调用Kind方法对其类型进行判断, 若不是通过指针创建的Value对象, 一定不能赋值.

    以下所有的赋值操作, 都可以与取值操作联动进行.

    基础类型

    a := int64(3)
    valueOf := reflect.ValueOf(a)
    // 此方法用于判断 Value 对象是否可以赋值
    valueOf.CanSet()
    // 因为是指针, 所以这里需要进行取值操作
    valueOf.Elem().SetInt(20)
    fmt.Println(a)
    

    结构体

    结构体的赋值与上面的获取属性值相同, 使用指针获取Value对象, 然后对其基础类型赋值.

    需要注意的一点, 结构体只有公共字段才能够通过反射进行赋值, 若赋值给一个私有字段, 会抛出异常.

    u := struct {
      Name string
      Age  int
    }{"xiao ming", 20}
    
    valueOf := reflect.ValueOf(&u)
    valueOf.Elem().Field(0).SetString("xiao hei")
    fmt.Println(u)
    

    数组

    其基础的Set方法确实提供了很多, 但是我查了一圈, 对于数组类型怎么赋值呢? 于是我看到了这个方法:

    func (v Value) Set(x Value)

    这个Set方法, 接收的参数是Value对象? 那不就成了么. 注意, Set是直接进行替换, 而不是追加.

    l := []int{1, 2, 3}
    
    valueOf := reflect.ValueOf(&l)
    // 创建一个数组用于后面进行赋值
    // 注意, 数组类型要相同
    setValueOf := reflect.ValueOf([]int{4, 5})
    valueOf.Elem().Set(setValueOf)
    fmt.Println(l)
    
    // 修改指定索引的值
    // 通过指针, 将指定索引的值取出来后, 进行赋值
    valueOf.Elem().Index(0).SetInt(9)
    fmt.Println(l)
    

    map

    m := map[string]string{
      "a": "1",
    }
    valueOf := reflect.ValueOf(&m)
    // 给指定的 key 设置
    valueOf.Elem().SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf("2"))
    fmt.Println(m)
    

    创建空值 Value

    除了上面的赋值操作, 还有一种不需要判断对象类型的方式, 通过方法New, 可以创建一个相同类型的空值Value对象, 返回的是一个指针的Value类型.

    此操作的好处是, 在使用过程中, 完全不需要判断对象类型.

    a := int64(3)
    // 创建一个相同类型内容. 返回的是一个指针
    fmt.Println(reflect.New(reflect.TypeOf(a)).Elem())
    

    变量方法的操作

    普通方法

    普通方法指未依附与结构体的方法.

    func add(a, b int) int {
    	return a + b
    }
    
    func main() {
    	valueOf := reflect.ValueOf(add)
    	// 构造函数参数
    	paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}
    	// 调用函数. 返回一个 Value 数组
    	retList := valueOf.Call(paramList)
    	// 获取返回值
    	fmt.Println(retList[0].Int())
    }
    

    结构体方法

    获取方法信息

    这里需要注意, 结构体指针和对象所拥有的方法数量是不同的, 具体可看: https://hujingnb.com/archives/348

    type User struct {
    	Name string
    }
    
    func (u User) GetName() string {
    	return u.Name
    }
    
    func (u *User) SetName(name string) {
    	u.Name = name
    }
    
    func main() {
    
    	u := User{}
    	typeOf := reflect.TypeOf(&u)
    	// 获取结构体中方法数量. 私有方法是拿不到的
    	fmt.Println(typeOf.NumMethod())
    	// 获取第0个方法, 返回 Method 对象. 下面介绍
    	fmt.Println(typeOf.Method(0))
    	// 根据方法名获取, 返回 Method 对象
    	fmt.Println(typeOf.MethodByName("GetName"))
    
    	/* Method 对象 */
    	setNameFunc, _ := typeOf.MethodByName("GetName")
    	// 方法名
    	fmt.Println(setNameFunc.Name)
    	// 方法签名
    	fmt.Println(setNameFunc.Type)
    	fmt.Println(setNameFunc.Index)
    	// 字段的可访问的包名. 公共方法为空
    	fmt.Println(setNameFunc.PkgPath)
    }
    

    方法调用

    type User struct {
    	Name string
    }
    
    func (u User) GetName() string {
    	return u.Name
    }
    
    func (u *User) SetName(name string) {
    	u.Name = name
    }
    
    func main() {
    
    	u := User{}
    	valueOf := reflect.ValueOf(&u)
    	// 获取结构体中方法数量. 私有方法是拿不到的
    	fmt.Println(valueOf.NumMethod())
    	// 获取第0个方法, 返回 Method 对象. 下面介绍
    	fmt.Println(valueOf.Method(0))
    	// 根据方法名获取, 返回 Method 对象
    	fmt.Println(valueOf.MethodByName("GetName"))
    
    	/* Method 对象 */
    	setNameFunc := valueOf.MethodByName("SetName")
    	// 调用方法
    	params := []reflect.Value{reflect.ValueOf("xiao ming")}
    	setNameFunc.Call(params)
    	// 此时对象的值已经改变
    	fmt.Println(u)
    	// 接收方法返回值
    	getNameFunc := valueOf.MethodByName("GetName")
    	fmt.Println(getNameFunc.Call([]reflect.Value{}))
    }
    

    原文地址: https://hujingnb.com/archives/676

  • 相关阅读:
    PHP获取汉字拼音首字母
    记录,待总结5
    HDU2833 WuKong Floyd
    搜索
    记录,待总结4
    HDU3350 #define is unsafe 栈的应用
    指针与引用的混合使用总结
    多源最短路径 Floyd
    引用总结
    函数返回值总结
  • 原文地址:https://www.cnblogs.com/hujingnb/p/15483728.html
Copyright © 2011-2022 走看看