golang 反射reflect

发布时间 2023-03-22 21:11:43作者: 潇潇暮鱼鱼

1.基本介绍

1)反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)

2)如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)

3)通过反射,可以修改变量的值,可以调用关联的方法

4)通过反射,需要import("reflect")

package reflect :import "reflect"

reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。

反射重要的函数和概念

1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型。

2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型。reflect.Value是一个结构体类型。通过reflect.Value可以获取到关于该变量的很多信息。

3)变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。

 

 

 

 2.快速入门

1)编写一个案例,演示对(基本数据类型,interface{},reflect.Value)进行反射的基本操作

反射是运行时的反射,所以在运行时能通过reflect.Value取结构体的字段,但是编译时通不过,所以会报错不给运行。需要先转换成interface{}再进行断言转成原来的数据类型,才可以继续操作

package main

import (
    "fmt"
    "reflect"
)

func testreflect(n interface{}) {
    //1.interface{} -> reflect.Type
    rType := reflect.TypeOf(n)
    fmt.Println(rType)
    //2.interface{} -> reflect.Value
    rVal := reflect.ValueOf(n)
    fmt.Println(rVal)
    //3.reflect.Value -> num
    //分解成两步
    //3.1reflect.Value -> interface{}
    iv := rVal.Interface()
    //3.2interface{} -> num 通过断言进行转换
    //a.使用ok进行类型判断
    //num2, ok := iv.(int)
    //if ok {
    //    //num是int类型可以自由的加减操作
    //    fmt.Println(num2 + 1)
    //}
    //stu, ok := iv.(Student)
    //if ok {
    //    fmt.Println(stu)
    //}

    //b.使用switch进行类型断言
    switch t := iv.(type) {
    case int:
        fmt.Println("int:", t)
    case Student:
        fmt.Println("Student:", t)
    default:
        fmt.Println("没有类型匹配")
    }

}

type Student struct {
    Name string
    Age  int
}

func main() {
    var num int = 100
    student := Student{"张三", 23}
    testreflect(num)
    testreflect(student)
}

3.反射的细节和注意事项

1)reflect.Value.Kind,获取变量的类别,返回的是一个常量。reflect.Type.Kind与reflect.Value.Kind是相同的。

2)Type是类型,Kind是类别。Type和Kind可能是相同的,也可能是不同的。比如int的Type和Kind都是int,结构体Student的Type是包名.Student,Kind是struct。所以Kind的范围是要比Type大的

3)通过反射可以让变量在interface{}和Reflect.Value之间相互转换

4)使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(),而不腻使用其他的,否则报panic

5)通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能丐帮传入的变量的值,同事需要使用到reflect.Value.Elem()方法

package main

import (
    "fmt"
    "reflect"
)

func reflect02(n interface{}) {
    //取reflect.Value值
    n1 := reflect.ValueOf(n)
    //n1.Elem()取指针对应的值
    //SetInt设置int类型的数值
    n1.Elem().SetInt(200)

}
func main() {
    num := 100
    //因为要改变传参的数值,所以用指针
    reflect02(&num)
    fmt.Println(num)
}

 

常量的特点:

1)常量使用const修改

2)常量在定义的时候,必须初始化

3)常量不能修改

4)常量只能修饰bool、数值类型(int,float系列),string类型

5)语法 const identifier [type] = value

常用写法:a=iota为0下面一次加1

const (
a = iota
b
c
d
)
fmt.Println(a, b, c, d) // 0,1,2,3

 4.反射的最佳实践

1)使用反射来遍历结构体的字段,调整结构体的方法,并获取结构体的标签

获得结构体的方法

func (v Value) Method(i int) Value

调用结构体的方法

func(v Value) Call(in []Value) []Value

package main

import (
    "fmt"
    "reflect"
)

type Monster struct {
    Name  string `json:"monster_name"`
    Age   int    `json:"monster_age"`
    Skill string `hello:"skill_1"`
    Sex   string
}

func (monster Monster) Print() {
    fmt.Println("----start----")
    fmt.Println(monster)
    fmt.Println("----end----")

}

func (monster Monster) Getsum(n1 int, n2 int) int {
    return n1 + n2
}

func (monster Monster) set(name string, age int, skill string, sex string) {
    monster.Name = name
    monster.Age = age
    monster.Skill = skill
    monster.Sex = sex
}

func reflect01(monster interface{}) {
    rv := reflect.ValueOf(monster)
    typ := reflect.TypeOf(monster)
    kd := rv.Kind()
    //若参数不为结构体则退出
    if kd != reflect.Struct {
        fmt.Println("expect struct")
        return
    }
    //获取字段个数
    rvn := rv.NumField()
    fmt.Printf("结构体有%d个字段\n", rvn)
    //遍历每个字段及其tag
    for i := 0; i < rvn; i++ {
        fmt.Printf("第%v个字段值为%v\n", i, rv.Field(i))
        //使用type的方法获得字段的tag的值
        tagVal := typ.Field(i).Tag.Get("hello")
        if tagVal != "" {
            fmt.Printf("Filed %d:tag为=%v\n", i, tagVal)
        }
    }
    //获取方法个数
    rvmd := rv.NumMethod()
    fmt.Printf("结构体有%d个方法\n", rvmd+1)
    //获得(Method)并调用(Call)方法
    //方法的排序是按照 函数名的排序(ASCII码)
    rv.Method(1).Call(nil) //获得第二个方法,调用它

    //调用结构体的第1个方法Method(0)
    //func (v Value) Call(in []Value) []Value
    //声明了reflect.Value的切片
    var params []reflect.Value
    params = append(params, reflect.ValueOf(10))
    params = append(params, reflect.ValueOf(40))

    res := rv.Method(0).Call(params) //传入的参数是[]reflect.Value,
    //调用call得到的res也是切片[]reflect.Value,所以需要res[0]取出数值
    fmt.Println("res=", res[0].Int())
}
func main() {
    monster := Monster{
        Name:  "牛魔王",
        Age:   150,
        Skill: "牛魔拳",
        Sex:   "",
    }
    //调用反射函数,通过该函数得到结构体的字段并执行其方法
    reflect01(monster)
}

2.在上一题的基础上,修改字段的值,“牛魔王”改为“白象精”

package main

import (
    "fmt"
    "reflect"
)

type Monster struct {
    Name  string `json:"monster_name"`
    Age   int    `json:"monster_age"`
    Skill string `hello:"skill_1"`
    Sex   string
}

func (monster Monster) Print() {
    fmt.Println("----start----")
    fmt.Println(monster)
    fmt.Println("----end----")

}

func (monster Monster) Getsum(n1 int, n2 int) int {
    return n1 + n2
}

func (monster Monster) set(name string, age int, skill string, sex string) {
    monster.Name = name
    monster.Age = age
    monster.Skill = skill
    monster.Sex = sex
}

func reflect01(monster interface{}) {
    rv := reflect.ValueOf(monster)
    typ := reflect.TypeOf(monster)
    kd := rv.Kind()
    //若参数不为指针则退出
    if kd != reflect.Ptr {
        fmt.Println("expect struct")
        return
    }
    //获取字段个数
    rvn := rv.Elem().NumField()
    fmt.Printf("结构体有%d个字段\n", rvn)
    //修改字段参数
    rv.Elem().Field(0).SetString("白象精")
    //遍历每个字段及其tag
    for i := 0; i < rvn; i++ {
        fmt.Printf("第%v个字段值为%v\n", i, rv.Elem().Field(i))
        //使用type的方法获得字段的tag的值
        tagVal := typ.Elem().Field(i).Tag.Get("hello")
        if tagVal != "" {
            fmt.Printf("Filed %d:tag为=%v\n", i, tagVal)
        }
    }
    //获取方法个数
    rvmd := rv.Elem().NumMethod()
    fmt.Printf("结构体有%d个方法\n", rvmd+1)
    //获得(Method)并调用(Call)方法
    //方法的排序是按照 函数名的排序(ASCII码)
    rv.Method(1).Call(nil) //获得第二个方法,调用它

    //调用结构体的第1个方法Method(0)
    //func (v Value) Call(in []Value) []Value
    //声明了reflect.Value的切片
    var params []reflect.Value
    params = append(params, reflect.ValueOf(10))
    params = append(params, reflect.ValueOf(40))

    res := rv.Elem().Method(0).Call(params) //传入的参数是[]reflect.Value,
    //调用call得到的res也是切片[]reflect.Value,所以需要res[0]取出数值
    fmt.Println("res=", res[0].Int())
}
func main() {
    monster := Monster{
        Name:  "牛魔王",
        Age:   150,
        Skill: "牛魔拳",
        Sex:   "",
    }
    //调用反射函数,通过该函数得到结构体的字段并执行其方法
    reflect01(&monster)
}