美文网首页
29 Golang反射与底层编程

29 Golang反射与底层编程

作者: learninginto | 来源:发表于2021-04-13 18:56 被阅读0次
什么情况下用到反射

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

  • 空接口可以存储任意类型的变量,那如何知道这个空接口保存数据的类型是什么?值是什么?
  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反射与底层编程

    什么情况下用到反射 有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也...

  • Golang的反射reflect深入理解和示例

    [TOC] Golang的反射reflect深入理解和示例 【记录于2018年2月】 编程语言中反射的概念 在计算...

  • 关于反射

    Golang的反射reflect深入理解和示例 编程语言中反射的概念 在计算机科学领域,反射是指一类应用,它们能够...

  • Golang 反射实现依赖注入

    Golang 反射实现依赖注入 Coding/Golang #Golang #Golang/reflect 依赖注...

  • golang反射用法举例(注册插件)

    有关golang反射的内容,网上有大量讲述,请自行google——"golang反射三法则" 下面主要反射在实际中...

  • Golang源码之Channel

    引用 图解Golang的channel底层原理 深入理解Golang Channel Go语言设计与实现-Chan...

  • 反射 reflection golang

    原文链接:反射reflection-GOLANG

  • Golang 反射的使用

    声明: 转载自golang reflect包,反射学习与实践[https://segmentfault.com/a...

  • Golang 反射

    基本了解 在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个Struct 你想要一个值,你...

  • golang 反射

    反射机制是指在程序运行的时候动态地获取对象的属性后者调用对象方法的功能。golang 支持反射,原生的 json ...

网友评论

      本文标题:29 Golang反射与底层编程

      本文链接:https://www.haomeiwen.com/subject/kjbclltx.html