go反射实现原理

1、interface原理

interface回顾
首先我们简单的回顾一下interface的结构,总体上是:
在这里插入图片描述
细分下来分为有函数的 iface和无函数的 eface(就是 interface{});
无函数的 eface
在这里插入图片描述
有函数的 iface
在这里插入图片描述

2、静态类型(static interface type)和动态混合类型(dynamic concrete type)

Go语言中,每个变量都有唯一个静态类型,这个类型是编译阶段就可以确定的。有的变量可能除了静态类型之外,还会有动态混合类型。
例如以下例子:

//带函数的interface
var r io.Reader 
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty
//不带函数的interface
var empty interface{}
empty = tty

有函数的 iface的例子
我们一句一句来看:第1行, varr io.Reader
在这里插入图片描述
第4行至第7行就是简单的赋值,得到一个 *os.File的实例,暂且不看了。
最后一句 r=tty
在这里插入图片描述
无函数的 eface的例子
我们接着往下看, varemptyinterface{}
在这里插入图片描述
最后是 empty=tty
在这里插入图片描述
但是记住:虽然有动态混合类型,但是对外"表现"依然是静态类型。

2、Go反射简介

反射简介

编译时不知道类型的情况下,通过反射机制可以获取对象的类型、值、方法甚至`动态改变对象的成员,这就是反射机制

Go反射有三大法则

//接口数据  =====》 反射对象
1. Reflection goes from interface value to reflection object.
//反射对象 ===> 接口数据
2. Reflection goes from reflection object to interface value.
// 倘若数据可更改,可通过反射对象来修改它
3. To modify a reflection object, the value must be settable.  

法则一:接口=》反射对象
在这里插入图片描述

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)
    fmt.Println("type:", t)   //type: float64
    fmt.Println("value:", v.String())  //value: <float64 Value>
    fmt.Println("type:", v.Type()) // type: float64
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64) //kind is float64: true
    fmt.Println("value:", v.Float()) //value: 3.4
}

由代码17行可以看出:Value还可以获取到当前数据值的 Type。所以,法则一的图应为:
(1)通过v.String()获取类型
(2)通过v.Type()获取类型
在这里插入图片描述

package main

import (
    "fmt"
    "reflect"
)

type temprature int

func main() {
    var temp interface{} = temprature(5)
    fmt.Printf("temprature is %d\n", temp.(temprature))
    itype := reflect.TypeOf(temp)
    ivalue := reflect.ValueOf(temp)
    fmt.Printf("%v: %v", itype, ivalue)

reflect.TypeOf()可以获取interface里值的类型
interface中name.(type)效果一致。
法则二:反射对象=》接口
在这里插入图片描述
注意 Type是没法逆向转换

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)
    ...
    o := v.Interface().(float64) // 法则2代码
    fmt.Println(o)
}

法则三:修改原数据的值
法则三是说:通过反射对象,可以修改原数据中的内容。
这里说的反射对象,是指 Value,毕竟 Type只是表示原数据的类型相关的内容,而 Value是对应着原数据对象本身。
如何通过反射对象来修改原数据对象的值呢?
如何才能可以通过反射对象来修改原数据对象的值或者说为什么不能设置呢?

原因简单且纯粹:在Go中,任何函数的参数都是值的拷贝,而非原数据
反射函数 reflect.ValueOf()也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象;即使可以修改,修改一个传参时候的副本,也毫无意义,不如报错儿。Go反射第三法则中的制定的 settable属性就由此而来,还延伸出了类似于 CanSet()的方法。

那如何修改呢?
(1)首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。

    ...
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    ...

(2)此时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v),如何做?Go提供了另外一个方法 Elem()

    ...
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    p := v.Elem()
    fmt.Println(p.CanSet()) // true
    p.SetFloat(7.1)
    fmt.Println(x) // 7.1

反射原理

不难发现,go的反射和interface在结构上是如此的相近!都分为两部分:一部分是 Type一部分是 value。

反射会不会是比着interface来实现的?

反射是什么意思?反射的意思是在运行时,能够动态知道给定数据对象的类型和结构,并有机会修改它!现在一个数据对象,如何判断它是什么结构?数据interface中保存有结构数据呀,只要想办法拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了呀~ 其实以上就是Go反射通俗的原理。
在这里插入图片描述

反射的使用原理

在这里插入图片描述
我们学习反射,其实就是学习如何使用原变量,去取得reflect.Type或者reflect.Value这种反射对象;再使用这个反射对象Type以及Value,反过来对原变量进行操作

反射的注意事项与细节

Type与Kind的区别是什么?
Type类型Kind类别,听起来有点绕,他们之间的关系为TypeKind的子集

如果变量是基本类型,那么Type与Kind得到的结果是一致的,比如变量为int类型,Type与Kind的值相等,都为int
但当变量为结构体时,Type与Kind的值就不一样了

func main() {
    var emp Employee
    emp = Employee{
        Name: "naonao",
        Age:  99,
    }
    rVal := reflect.ValueOf(emp)
    log.Printf("Kind is %v ,Type is %v",
        rVal.Kind(),
        rVal.Type())
    // Kind is struct ,Type is main.Employee
}

可以看到,Kind的值是struct,而Type的值是包名.Employee
反射如何在变量与reflect.Value之间切换?
变量可以转换成interface{}之后,再转换成reflect.Value类型,既然空接口可以转换成Value类型,那么自然也可以反过来转换成变量
用个表达式来表示,就如下所示

变量<----->interface{}<----->reflect.Value

利用空接口来进行中转,这样变量Value之间就可以实现互相转换了

如何使用反射获取变量本身的值?
这里我们要注意一下,reflect.ValueOf()得到的值是reflect.Value类型,并不是变量本身的值

var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
这段代码会报错

> invalid operation: num + rVal (mismatched types int and reflect.Value)

很明显,rVal是属于reflect.Value类型,不能与int类型相加

那怎样才能获得它本身的值呢?

如果是基本类型,比如var num int,那么使用reflect包里提供的转换方法即可reflect.ValueOf(num).Int()

或者是float,那就调用reflect.ValueOf(num).float(),如果是其它的基本类型,需要的时候去文档里面找找即可.

但如果是我们自己定义的结构体,因为reflect包无法确定我们自己定义了什么结构体,所以本身并不会带有结构体转换的方法,那么我们只能通过类型断言来进行转换

也就是上面说的,利用空接口进行中转,再利用断言进行类型转换,可以看如下代码示例

// Employee 员工
type Employee struct {
    Name string
    Age  int
}

func main() {
    emp := &Employee{
        Name: "naonao",
        Age:  99,
    }
    reflectPrint(emp)
}

func reflectPrint(v interface{}) {
    rVal := reflect.ValueOf(v)   // 获取reflect.Value
    iV := rVal.Interface()       // 利用空接口进行中转
    empVal, ok := iV.(*Employee) // 利用断言转换
    if ok {
        // 如果成功转换则打印结构体
        log.Print(empVal)
    }
}

通过反射来修改变量
先来看看代码如何实现

func main() {
    var num = 1
    modifyValue(&num)// 传递地址
    log.Printf("num is %v", num)// num is 20
}

func modifyValue(i interface{}) {
    rVal := reflect.ValueOf(i)
    rVal.Elem().SetInt(20)
}

细心的你肯定发现了一点异常,函数接收的参数不再是了,而是接受了一个指针地址

改变值的时候,先调用了Elem()方法,再进行了一个SetInt()的操作

为什么直接传值不行呢?因为reflect包中提供的所有修改变量值的方法,都是对指针进行的操作

那为什么还要先使用Elem()呢?因为Elem()的作用,就是取得指针地址所对应的值,取到值了,我们才能对值进行修改

反射的实践学习

1、反射和interface 的区别

1、类型
反射通过reflect.TypeOf()获取类型,interface也可以通过.(type)获取类型
区别:
(1)reflect.TypeOf()的返回值是reflect.Type,interface中.(type)获取的是一个包含类型结构体。
(2)reflect.TypeOf()可以获取一个结构体中的字段类型,interface只能获取结构体类型,无法获取字段类型。
2、值
(1)interface取得就是值本身

empVal :=interface_name.(*Employee)

(2)reflect.ValueOf()的返回值是reflect.Value,必须通过interface进行转换获取自身的值。

rVal := reflect.ValueOf(v)   // 获取reflect.Value
iV := rVal.Interface()       // 利用空接口进行中转
empVal, ok := iV.(*Employee) // 利用断言转换

3、函数
reflect可以通过获取的结构体值获取结构体实现的方法,并执行。

method = rVal.MethodByName(methodByName)
result := method.Call(params)

在这里插入图片描述

2、reflect代码实践

定义结构体和方法

// Employee 员工
type Employee struct {
    Name string `json:"emp_name"`
    Age  int    `json:"emp_age"`
    Sex  int
}

// GetSum 返回两数之和
func (e *Employee) GetSum(n1, n2 int) int {
    return n1 + n2
}

// Set 接受值,给结构体e赋值
func (e *Employee) Set(name string, age, sex int) {
    e.Name = name
    e.Age = age
    e.Sex = sex
}

// Print 打印结构体*Employee 
func (e *Employee) Print() {
    log.Print("======Start======")
    log.Print(e)
    log.Print("======End======")
}

使用反射来遍历结构体的字段值,并获取结构体的tag标签

// GetStruct 获取结构体的字段及tag
func GetStruct(i interface{}) {
    rType := reflect.TypeOf(i)
    rVal := reflect.ValueOf(i)

    kd := rVal.Kind()

    // 如果是传进来的是指针类型
    // 则获取指针值
    if kd == reflect.Ptr {
        rType = rType.Elem()
        rVal = rVal.Elem()
        kd = rVal.Kind()
    }

    if kd != reflect.Struct {
        log.Panicf("Kind is %v not struct ", kd)
    }
    // 获取结构体的字段数
    sNum := rVal.NumField()
    log.Printf("Struct has %v fields ", sNum)
    // 遍历结构体的所有字段
    for i := 0; i < sNum; i++ {
        log.Printf("Field %d value is %v", i, rVal.Field(i))
        // 获取Struct的tag,使用Type类型获取
        tag := rType.Field(i).Tag.Get("json")
        if tag == "" {
            log.Printf("Field %d hasn't tag  %v ", i, tag)
            continue
        }
        log.Printf("Field %d tag is %v ", i, tag)
    }
}

获取并调用结构体的方法

// CallMethod 调用结构体方法
// i : 传入的struct
// methodByName : 调用结构体的方法名
func CallMethod(i interface{}, methodByName string) {
    rVal := reflect.ValueOf(i)
    rType := reflect.TypeOf(i)
    log.Printf("Type is %v Kind is %v", rType, rType.Kind())

    // 获取结构体有多少个方法
    numOfMethod := rVal.NumMethod()
    log.Printf("Struct has %d method", numOfMethod)
    // 声明Value数组
    var params []reflect.Value
    // 声明一个Value类型,用于接收方法
    var method reflect.Value

    if methodByName == "GetSum" {
        // 调用方法时的参数
        params = append(params, reflect.ValueOf(10))
        params = append(params, reflect.ValueOf(88))

    }
    if methodByName == "Set" {
        // 调用方法时的参数
        params = append(params, reflect.ValueOf("闹闹吃鱼"))
        params = append(params, reflect.ValueOf(18))
        params = append(params, reflect.ValueOf(0))
    }
    // 获取方法
    method = rVal.MethodByName(methodByName)
    if !method.IsValid() {
        // 如果结构体不存在此方法,输出Panic
        log.Panic("Method is invalid")
    }
    result := method.Call(params)
    if len(result) > 0 {
        // 如果函数存在返回值,则打印第一条
        log.Println("Call result is ", result[0])
    }
}

修改字段值

// ModifyField 修改字段值
func ModifyField(i interface{}, filedName string) {
    rVal := reflect.ValueOf(i)
    filed := rVal.Elem().FieldByName(filedName)
    if !filed.IsValid() {
        log.Panic("filedName is invalid")
    }
    filed.SetString("闹闹")
}

定义适配器,用作统一处理接口

// Bridge 适配器
// 可以实现调用任意函数
func Bridge(call interface{}, args ...interface{}) {
    var (
        function reflect.Value
        inValue  []reflect.Value
    )
    n := len(args)
    // 将参数转换为Value类型
    inValue = make([]reflect.Value, n)
    for i := 0; i < n; i++ {
        inValue[i] = reflect.ValueOf(args[i])
    }
    // 获得函数的Value类型
    function = reflect.ValueOf(call)
    // 传参,调用函数
    function.Call(inValue)
}

测试

func TestBridge(t *testing.T) {
    call1 := func(v1, v2 int) {
        log.Println(v1, v2)
    }
    call2 := func(v1, v2 int, str string) {
        log.Println(v1, v2, str)
    }

    Bridge(call1, 1, 2)
    Bridge(call2, 2, 3, "callTest")
}

使用反射创建,并操作结构体

// CreateStruct 使用反射创建结构体
// 并给结构体赋值
func CreateStruct(i interface{}) *Employee {
    var (
        structType  reflect.Type
        structValue reflect.Value
    )
    // 获取传入结构体指向的Type类型
    structType = reflect.TypeOf(i).Elem()
    // 创建一个结构体
    // structValue持有一个指向类型为Type的新申请的指针
    structValue = reflect.New(structType)
    // 转换成我们要创建的结构体
    modle := structValue.Interface().(*Employee)
    // 取得structValue指向的值
    structValue = structValue.Elem()
    // 给结构体赋值
    structValue.FieldByName("Name").SetString("闹闹吃鱼")
    structValue.FieldByName("Age").SetInt(100)
    return modle
}

学习链接:https://blog.csdn.net/i6448038/article/details/104337837
https://www.jianshu.com/p/9816a7a551cd
https://www.jianshu.com/p/32e4cf8ffffb
https://www.jianshu.com/p/53adb1e92710

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值