29 Golang反射与底层编程

什么情况下用到反射

有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射。

  • 空接口可以存储任意类型的变量,那如何知道这个空接口保存数据的类型是什么?值是什么?
  1. 可以使用类型断言
  2. 可以使用反射实现,也就是在程序运行时动态地获取一个变量的类型信息和值信息。
  • 把结构体序列化成json字符串,自定义结构体Tab标签的时候就用到了反射
反射的基本介绍

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go可以实现的功能
  1. 反射可以在程序运行期间动态地获取变量的各种信息,比如变量的类型、类别
  2. 如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、方法
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  • Go语言的变量分为两个部分
  1. 类型信息:预先定义好的元信息
  2. 值信息:程序运行过程中可动态变化的

在Go语言中,反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,并且reflect包提供了reflect.TypeOfreflect.ValueOf两个重要的函数来获取任意对象的Type和Value

  • reflect.TypeOf()获取任意值的类型对象
type myInt int

type Person struct {
    Name string
    Age  int
}

func reflectFn(x interface{}) {
    v := reflect.TypeOf(x)
    fmt.Println(v)
}

func main() {
    a := 10
    b := 23.4
    c := "hello"
    d := true
    reflectFn(a)
    reflectFn(b)
    reflectFn(c)
    reflectFn(d)
    var e myInt = 34
    var f = Person{
        Name: "张三",
        Age:  20,
    }
    var g = 20
    reflectFn(e)
    reflectFn(f)
    reflectFn(&g)
    
//int
//float64
//string
//bool
//main.myInt
//main.Person
//*int

}
  • type Name和type Kind

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类Kind。

type myInt int

type Person struct {
    Name string
    Age  int
}

func reflectFn(x interface{}) {
    v := reflect.TypeOf(x)
    //v.Name()//类型名称
    //v.Kind()//种类
    fmt.Printf("类型:%v 类型名称:%v 类型种类:%v", v, v.Name(), v.Kind())
    fmt.Println(v)
}

func main() {
    a := 10
    b := 23.4
    c := "hello"
    d := true
    reflectFn(a)
    reflectFn(b)
    reflectFn(c)
    reflectFn(d)
    var e myInt = 34
    var f = Person{
        Name: "张三",
        Age:  20,
    }
    var g = 20
    var h = [3]int{1, 3, 5}
    var i = []int{11, 22, 33}
    reflectFn(e)
    reflectFn(f)
    reflectFn(&g)
    reflectFn(h)
    reflectFn(i)
}

//类型:int 类型名称:int 类型种类:intint
//类型:float64 类型名称:float64 类型种类:float64float64
//类型:string 类型名称:string 类型种类:stringstring
//类型:bool 类型名称:bool 类型种类:boolbool
//类型:main.myInt 类型名称:myInt 类型种类:intmain.myInt
//类型:main.Person 类型名称:Person 类型种类:structmain.Person
//类型:*int 类型名称: 类型种类:ptr*int
//类型:[3]int 类型名称: 类型种类:array[3]int
//类型:[]int 类型名称: 类型种类:slice[]int
reflect.ValueOf()
  • 空接口类型和整型计算(使用类型断言)
type Person struct {
    Name string
    Age  int
}

func reflectValue(x interface{}) {
    b, _ := x.(int)
    var num = 10 + b
    fmt.Println(num)
}

func main() {
    var a = 13
    reflectValue(a)
}

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值 的信息。reflect.Value与原始值之间可以互相转换。

type myInt int

type Person struct {
    Name string
    Age  int
}

func reflectValue(x interface{}) {
    //b, _ := x.(int)
    //var num = 10 + b
    //fmt.Println(num)
    v := reflect.ValueOf(x)
    //var n = v + 12//mismatched types reflect.Value and int
    fmt.Println(v)//13
    
    //反射获取变量的原始值
    var m = v.Int() + 12
    fmt.Println(m) //25
    
}

func main() {
    var a = 13
    reflectValue(a)
}

reflect.Value类型提供的获取原始值的方法如下:

方法 说明
Interface interface 将值以interface{}类型返回
Int() int64 将值以int类型返回
Uint() unit64 将值以uint类型返回
Float() float64 将值以双精度(float)类型返回
Bool() bool 将值以bool类型返回
Bytes() []bytes 将值以自己数组[]bytes类型返回
String() string 将值以字符串类型返回
…… ……
  • demo
func reflectValue(x interface{}) {
    v := reflect.ValueOf(x)
    kind := v.Kind()

    switch kind {
    case reflect.Int:
        fmt.Println("int类型的原始值+10", v.Int()+10)
    case reflect.Float32:
        fmt.Println("Float32类型的原始值+10", v.Float()+10)
    case reflect.Float64:
        fmt.Println("Float64类型的原始值+10", v.Float()+10)
    case reflect.String:
        fmt.Println("String类型的原始值", v.String())
    default:
        fmt.Println("暂未判断该类型")
    }
}

func main() {
    var a int64 = 100
    var b float32 = 3.14
    var c string = "你好golang"
    reflectValue(a)
    reflectValue(b)
    reflectValue(c)
}

//暂未判断该类型
//Float32类型的原始值+10 13.140000104904175
//String类型的原始值 你好golang

  • 通过反射改变原始值

v.Elem().Kind()获取原始类型,传入的值需要是指针

func reflectSetValue1(x interface{}) {
    v := reflect.ValueOf(x)
    if v.Kind() == reflect.Int64 {
        v.SetInt(120)
    }
}

func reflectSetValue2(x interface{}) {
    v := reflect.ValueOf(x)
    fmt.Println(v.Kind(), v.Elem().Kind())
    if v.Elem().Kind() == reflect.Int64 {
        v.Elem().SetInt(123)
    } else if v.Elem().Kind() == reflect.String{
        v.Elem().SetString("你好golang")
    }
}

func main() {
    var a int64 = 100

    //reflectSetValue1(a)
    // reflect.Value.SetInt using unaddressable value

    var b string = "Hello world"
    reflectSetValue2(&a)
    reflectSetValue2(&b)
    fmt.Println(a,b)
}

//ptr int64
//ptr string
//123 你好golang
结构体反射
  • StructField类型

StructField类型用来描述结构体中的一个字段的信息。

type StructField struct {
    Name string //字段名称
    PkgPath string //非导出字段的包路径,对导出字段该字段为""
    Type Type //字段的类型
    Tag StructTag //字段的标签
    Offset uintptr //字段在结构体中的字节偏移量
    Infex []int //用于Type.FieldByIndex时的索引切片
    Anonymous bool //是否匿名字段
}
  • 判断参数是否为结构体类型
type Student struct {
    Name  string `json:"name" form:"username"'`
    Age   int    `json:"age"`
    Score int    `json:"score"`
}

func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    //判断参数是否为结构体类型
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的参数不是结构体")
        return
    }

}

func main() {
    stu1 := Student{
        Name:  "小明",
        Age:   18,
        Score: 98,
    }
    PrintStructField(stu1)
}
  • 通过类型变量里的Field获取结构体的字段
func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    field0 := t.Field(0)
    fmt.Println("字段名称", field0.Name)
    fmt.Println("字段类型", field0.Type)
    fmt.Println("tag标签", field0.Tag.Get("json"))
    fmt.Println("字段Tag", field0.Tag.Get("form"))
    fmt.Println("--------------")
}

//字段名称 Name
//字段类型 string
//tag标签 name
//字段Tag username
//--------------
  • 通过类型变量里的FieldByName可以获取结构体的字段
func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    //v := reflect.ValueOf(s)
    field1, ok := t.FieldByName("Age")
    if ok {
        fmt.Println("字段名称", field1.Name)
        fmt.Println("字段类型", field1.Type)
        fmt.Println("tag标签", field1.Tag.Get("json"))
    }
    fmt.Println("--------------")
}
//字段名称 Age
//字段类型 int
//tag标签 age
//--------------
  • 通过类型变量里的NumField获取到该结构体有几个字段
func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    var fieldCount = t.NumField()
    fmt.Println("结构体有", fieldCount, "个属性")
    fmt.Println("--------------")
}
//结构体有 3 个属性
//--------------
  • 通过值变量获取结构体属性对应的值
func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    var fieldCount = t.NumField()
    fmt.Println(v.FieldByName("Name"))
    fmt.Println(v.FieldByName("Age"))
    for i := 0; i < fieldCount; i++ {
        fmt.Printf("属性名称:%v 属性值:%v 属性类型:%v 属性Tag:%v \n", t.Field(i).Name, v.Field(i), t.Field(i).Type, t.Field(i).Tag.Get("json"))
    }
}
//小明
//18
//属性名称:Name 属性值:小明 属性类型:string 属性Tag:name
//属性名称:Age 属性值:18 属性类型:int 属性Tag:age
//属性名称:Score 属性值:98 属性类型:int 属性Tag:score
  • 与结构体相关的方法

任意值通过reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()和Field()方法获得结构体成员的详细信息。
reflect.Type中与获取结构体成员相关的方法如下:

方法 说明
Field(i int)StructField 根据索引,返回索引对应的结构体字段的信息
NumField() int 返回结构体成员字段数量
FieldByName(name string)(StructField,bool) 根据给定字符串返回字符串对应的结构体字段的信息
FieldByIndex(index []int) StructField 多层成员访问时,根据[]int提供的每个结构体的字段索引,返回字段的信息
  • demo
type Student struct {
    Name  string `json:"name" form:"username"'`
    Age   int    `json:"age"`
    Score int    `json:"score"`
}

func (s Student) GetInfo() string {
    var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩%v", s.Name, s.Age, s.Score)
    return str
}

func (s *Student) SetInfo(name string, age int, score int) {
    s.Name = name
    s.Age = age
    s.Score = score
}

func (s Student) Print() {
    fmt.Println("这是一个打印方法")
}

func PrintStructFn(s interface{}) {
    //1.判断是否为结构体
    t := reflect.TypeOf(s)
    //v := reflect.ValueOf(s)
    if t.Kind() != reflect.Ptr {
        fmt.Println("传入的参数不是一个结构体指针类型")
        return
    } else if t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的参数不是一个结构体指针类型")
        return
    }
    
    //2.通过类型变量中的Method获取结构体的方法
    method0 := t.Method(0)    //和结构体方法的顺序没有关系,与ASCII有关
    fmt.Println(method0.Name) //GetInfo
    fmt.Println(method0.Type) //func(main.Student) string
    fmt.Println("-----------")

    //3.通过类型变量获取这个结构体有多少个方法
    method1, ok := t.MethodByName("Print")
    if ok {
        fmt.Println(method1.Name) //Print
        fmt.Println(method1.Type) //func(main.Student)
        fmt.Println("-----------")
    }

    v := reflect.ValueOf(s)
    //4.通过"值变量"执行方法(注意参数)v.Method(0).Call(nil)或v.MethodByName()
    v.Method(1).Call(nil) //这是一个打印方法
    info := v.MethodByName("GetInfo").Call(nil)
    fmt.Println(info) //[姓名:小明 年龄:18 成绩98]
    fmt.Println("------------")

    //5.执行方法传入参数(注意需要使用”值变量“,并且要注意参数,接收的参数是[]reflect.Value的切片)
    var params []reflect.Value
    params = append(params, reflect.ValueOf("小张"))
    params = append(params, reflect.ValueOf(19))
    params = append(params, reflect.ValueOf(95))
    v.MethodByName("SetInfo").Call(params) //执行方法传入参数
    newInfo := v.MethodByName("GetInfo").Call(nil)
    fmt.Println("通过反射改变属性的值:", newInfo)
    //6.获取方法数量
    fmt.Println("方法数量", t.NumMethod())//3
}

func main() {
    stu1 := Student{
        Name:  "小明",
        Age:   18,
        Score: 98,
    }
    PrintStructFn(&stu1)
}

注意:修改结构体属性时,必须传入结构体的地址,因为结构体是值类型

不要乱用反射

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行时才引发panic(那可能是在代码写完的很长时间之后)
  2. 大量使用反射的代码通常难以理解

你可能感兴趣的:(29 Golang反射与底层编程)