乐趣区

关于后端:Go学习二十七反射学习使用

1. 介绍

Go语言实现了反射,所谓反射就是动静运行时的状态。咱们个别用到的包是 reflect 包,reflect 实现了运行时的反射能力,可能让程序操作不同类型的对象。反射包中有两对十分重要的函数和类型,两个函数别离是:

办法名 形容
reflect.TypeOf 能够取得任意值的类型对象, 返回类型:reflect.Type
reflect.ValueOf 能够取得任意值的值对象, 返回类型:reflect.Value

反射包中的所有办法根本都是围绕着 reflect.Typereflect.Value 两个类型设计的。咱们通过 reflect.TypeOfreflect.ValueOf 能够将一个一般的变量转换成反射包中提供的 reflect.Typereflect.Value,随后就能够应用反射包中的办法对它们进行简单的操作。

2.reflect.Type

类型 reflect.Type 是反射包定义的一个接口,咱们能够应用 reflect.TypeOf 函数获取任意变量的类型,reflect.Type 接口中定义了一些办法, 罕用的如下:

2.1 罕用办法列表

办法名 形容
Kind() Kind 返回该变量的的具体分类
Name() string Name 返回该类型在本身包内的类型名,如果是未命名类型会返回 ””
NumField() int 返回 struct 字段数量,不是 structpanic
Implements(u Type) bool 查看以后类型有没有实现接口 u
Field(i int) StructField 返回struct 类型的第 i 个字段,不是structpanic,<br/>i 越界也会panic
Key() Type 返回 mapkey 的类型,不是mappanic

2.2 应用示例

package main
import (
    "fmt"
    "reflect"
)
type People interface {Eat(str string)
}
type User struct {
    Name string
    Age  int
}
func (u User) Eat(str string) {fmt.Println("吃" + str)
}
func main() {
    var user User
    userTypeOf := reflect.TypeOf(user)
    fmt.Printf("typeOf:%T v:%v\n", userTypeOf, userTypeOf)
    // 获取类型分类
    fmt.Printf("kind:%T v:%v\n", userTypeOf.Kind(), userTypeOf.Kind())
    // 获取类型名称
    fmt.Printf("name:%T v:%v\n", userTypeOf.Name(), userTypeOf.Name())
    // 判断类型是否实现接口 People
    implements := userTypeOf.Implements(reflect.TypeOf((*People)(nil)).Elem())
    fmt.Printf("变量 user 是否实现接口 People : %t\n", implements)
    // 变量构造体 user 的字段数量
    fmt.Printf("变量构造体 user 的字段数量: %d \n", userTypeOf.NumField())
    // 打印构造体字段
    fmt.Printf("打印构造体 user 第 %d 个字段: %v \n", 0,userTypeOf.Field(0).Name)
    fmt.Printf("打印构造体 user 第 %d 个字段: %v \n", 1,userTypeOf.Field(1).Name)
    map1 := map[string]int {
        "张三": 19,
        "李四": 23,
    }
    fmt.Printf("打印 map 的 key 类型: %v \n", reflect.TypeOf(map1).Key())
}

/** 输入
typeOf:*reflect.rtype v:main.User
kind:reflect.Kind v:struct
name:string v:User
变量 user 是否实现接口 People : true
变量构造体 user 的字段数量: 2 
打印构造体 user 第 0 个字段: Name 
打印构造体 user 第 1 个字段: Age 
打印 map 的 key 类型: string 
*/

2.3 Kind 类型整顿

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号 8 位整型
    Int16                // 有符号 16 位整型
    Int32                // 有符号 32 位整型
    Int64                // 有符号 64 位整型
    Uint                 // 无符号整型
    Uint8                // 无符号 8 位整型
    Uint16               // 无符号 16 位整型
    Uint32               // 无符号 32 位整型
    Uint64               // 无符号 64 位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64 位复数类型
    Complex128           // 128 位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 构造体
    UnsafePointer        // 底层指针
)

3.reflect.Value

反射包中 Value 的类型与 Type 不同,它被申明成了构造体。这个构造体没有对外裸露的字段,然而提供了获取或者写入数据的办法。

3.1 罕用办法列表

办法名 形容
Call(in []Value) []Value 调用办法, 第一个参数是in[0],第二个是in[1],以此类推
Field(i int) StructField 返回struct 类型的第 i 个字段的值,不是structpanic,<br/>i 越界也会panic
FieldByName(name string) Value 依据字段名查找值,前提类型是struct
Index(i int) Value 返回第 i 个元素, 次要用于遍历, 不能越界。<br/> 前提类型是 Array, Slice, String 之一,
IsNil() bool 返回值是否为 nil。如果值类型不是通道(channel)、函数、
接口、map、指针或 切片时产生 panic,相似于语言层的v== nil 操作。常被用于判断指针是否为空。
IsValid() bool 返回 v 是否持有一个值。如果 vValue零值会返回假,
此时 v 除了 IsValid、String、Kind 之外的办法都会导致 panic常被用于断定返回值是否无效
MapIndex(key Value) Value 依据索引获取对应的值, 前提类型是map
MapKeys() []Value 返回 map 中所有的key, 返回类型是切片slice
Set(x Value) 通过反射批改值。
Type() Type 获取值的类型

3.2 应用示例

package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Name string
    Age  int
}
func (u User) Eat(str string) {fmt.Println("调用函数: 吃" + str)
}
func main() {user := User{"张福生", 30}
    userValOf := reflect.ValueOf(user)
    // 调用办法
    param := []reflect.Value{reflect.ValueOf("香蕉")}
    userValOf.MethodByName("Eat").Call(param)
    // 打印构造体的值
    fmt.Printf("打印构造体 user 第 %d 个字段值 v: %v\n", 0, userValOf.Field(0))
    fmt.Printf("打印构造体 user 第 %d 个字段值 v: %v\n",1, userValOf.Field(1))
    // 依据属性值打印构造体的值
    fmt.Printf("打印构造体 user 字段 %s 的值 v: %v\n", "Age",userValOf.FieldByName("Age"))
    // 返回第 i 个元素, 次要用于遍历, 不能越界。前提类型是 Array, Slice, String 之一,strSlice :=[]string{"你","我","他","!"}
    strSliceValOf := reflect.ValueOf(strSlice)
    fmt.Printf("打印字符串第 %d 个元素值 v: %v\n", 0,strSliceValOf.Index(0))
    fmt.Printf("打印字符串第 %d 个元素值 v: %v\n", 1,strSliceValOf.Index(1))
    fmt.Printf("打印字符串第 %d 个元素值 v: %v\n", 3,strSliceValOf.Index(2))

    // IsNil: 判断是否是 nil
    fmt.Printf("变量 strSlice 的值是否是 nil: %t\n",strSliceValOf.IsNil())
    var u []User
    uuOf := reflect.ValueOf(u)
    fmt.Printf("IsNil: 变量 u 的值是否是 nil: %t\n",uuOf.IsNil())
    // IsValid:判断返回值是否无效
    fmt.Printf("存在的办法: %t\n",userValOf.MethodByName("Eat").IsValid())
    fmt.Printf("不存在的办法: %t\n",userValOf.MethodByName("A").IsValid())

    // #############  针对 map 的操作  #############
    map1 := map[string]string{
        "姓名": "张三",
        "phone": "176666666666",
        "?": "ABC",
    }
    mapValOf := reflect.ValueOf(map1)
    // 依据索引获取对应的值
    fmt.Printf("依据索引获取 map 值,key=%s,v=%s \n","姓名",mapValOf.MapIndex(reflect.ValueOf("姓名")))
    fmt.Printf("依据索引获取 map 值,key=%s,v=%s \n","phone",mapValOf.MapIndex(reflect.ValueOf("phone")))
    fmt.Printf("依据索引获取 map 值,key=%s,v=%s \n","?",mapValOf.MapIndex(reflect.ValueOf("?")))
    // 返回 map 中所有的 key, 返回类型是切片 slice
    fmt.Printf("返回 map 中所有的 key: %+v 类型:%T \n",mapValOf.MapKeys(),mapValOf.MapKeys())
    // 通过反射批改值
    num := 10
    numValOf := reflect.ValueOf(&num)// 留神这里须要传地址
    fmt.Printf("批改前的值: %v \n",num)
    numValOf.Elem().Set(reflect.ValueOf(100))
    fmt.Printf("批改后的值: %v \n",num)
    // 获取值的类型
    fmt.Printf("获取 mapValOf 值的类型: %v \n",mapValOf.Type())
}

输入:

调用函数: 吃香蕉
打印构造体 user 第 0 个字段值 v: 张福生
打印构造体 user 第 1 个字段值 v: 30
打印构造体 user 字段 Age 的值 v: 30
打印字符串第 0 个元素值 v: 你
打印字符串第 1 个元素值 v: 我
打印字符串第 3 个元素值 v: 他
变量 strSlice 的值是否是 nil: false
IsNil: 变量 u 的值是否是 nil: true
存在的办法: true
不存在的办法: false
依据索引获取 map 值,key= 姓名,v= 张三 
依据索引获取 map 值,key=phone,v=176666666666 
依据索引获取 map 值,key=?,v=ABC 
返回 map 中所有的 key: [姓名 phone ?] 类型:[]reflect.Value 
批改前的值: 10 
批改后的值: 100 
获取 mapValOf 值的类型: map[string]string 

4. 实际场景

4.1 动静调用构造体办法

应用 MethodByName("xx").Call(param) 调用函数

/**
 * @Author Mr.LiuQH
 * @Description 反射学习应用
 * @Date 2021/2/2 10:50 上午
 **/
package main
import (
    "fmt"
    "reflect"
)
type People struct {Name string}
func (p People) GetName() string {return p.Name}
func (p People) Tell(toName, context string) string {return fmt.Sprintf(p.Name+"通知 %s,%s", toName, context)
}

func main() {p := People{"张三"}
    peopleValOf := reflect.ValueOf(p)
    // 通过反射调用办法(无参数)
    call := peopleValOf.MethodByName("GetName").Call(nil)
    fmt.Printf("无参函数调用, 返回: %v %T \n", call, call)

    // 通过反射调用办法(有参数)
    param := []reflect.Value{reflect.ValueOf("小明"),
        reflect.ValueOf("你通过考试了!"),
    }
    values := peopleValOf.MethodByName("Tell").Call(param)
    fmt.Printf("有参函数调用, 返回: %v %T \n", values, values)
}
/** 输入
无参函数调用, 返回: [张三] []reflect.Value 
有参函数调用, 返回: [张三通知小明, 你通过考试了!] []reflect.Value 
*/

4.2 处理错误后果

package main
import (
    "errors"
    "fmt"
    "reflect"
)
type People struct {Name string}
func (p People)ThrowError()error  {return errors.New("测试谬误抛出")
}
func main() {p := People{"张三"}
    peopleValOf := reflect.ValueOf(p)
    ret := peopleValOf.MethodByName("ThrowError").Call(nil)
    // 返回值也是 Value 类型,对于谬误,能够转为 interface 之后断言
    fmt.Printf("errValue: %v 类型转换: %T", ret[0], ret[0].Interface().(error))
}

4.3 解析构造体标签

应用 field.Tag.Lookup()field.Tag.Get()查找对应的标签值

package main
import (
    "fmt"
    "reflect"
)
type People struct {
    Name string `json:"name" db:"t_name"`
    Age int `json:"age" db:"t_age"`
}
func main() {p := People{"张三",23}
    peopleTypeOf := reflect.TypeOf(p)
    // 获取构造体字段数量
    fieldNum := peopleTypeOf.NumField()
    for i := 0; i < fieldNum; i++ {
        // 获取构造体中,第 i 个字段的值
        field := peopleTypeOf.Field(i)
        // 获取 json 标签对应的值(办法一)
        if lookup, ok := field.Tag.Lookup("json");ok {fmt.Printf("字段 %v, 对应的 json-tag: %s \n" ,field.Name, lookup)
        }
        // 获取 db 标签对应的值(办法二)
        dbTag := field.Tag.Get("db")
        fmt.Printf("字段 %v, 对应的 db-tag: %s \n" ,field.Name, dbTag)
    }
}
/** 输入
字段 Name, 对应的 json-tag: name 
字段 Name, 对应的 db-tag: t_name 
字段 Age, 对应的 json-tag: age 
字段 Age, 对应的 db-tag: t_age 
*/

4.4 判断是否实现接口

package main
import (
    "fmt"
    "reflect"
)
type People interface {Eat(str string)
}
type User struct {}
func (u User) Eat(str string) {fmt.Println("吃" + str)
}
func main() {user := &User{}
    // 当变量不是传指针时,Eat 办法的接管方是指针,则没有实现接口
    userTypeOf := reflect.TypeOf(user)
    // 判断类型是否实现接口 People
    implements := userTypeOf.Implements(reflect.TypeOf((*People)(nil)).Elem())
    fmt.Printf("变量 user 是否实现接口 People : %t\n", implements)
}
// 输入: 变量 user 是否实现接口 People : false

本文由 mdnice 多平台公布

退出移动版