go(golang)语言的反射实现

什么是反射

在 Go 语言中,反射(Reflection)是指在程序运行时动态地获取变量的类型信息和值,并对其进行操作的能力。通过反射,我们可以在不知道具体类型的情况下,对变量进行类型检查、调用方法、获取和设置字段值等操作。

反射的实现

反射

go 语言的反射是通过接口实现的。而 go 中的接口变量其实是用 iface 和 eface 这两个结构体来表示的:
iface 表示某一个具体的接口(含有方法的接口)
eface 表示一个空接口(interface{})
go底层的类型信息是使用 _type 结构体来存储的。

type iface struct {
    tab  *itab // 方法表
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}
interfacetype的mhdr存放函数,itab._type存放类型
一个接口中包含了变量的类型信息和类型的数据。因此,我们才可以通过反射来获取到变量的类型信息,以及变量的数据信息。

在包 reflect 中实现了运行时的反射能力,反射包中有两对非常重要的函数和类型,它们与函数是一一对应的关系。

  • 两个函数
    reflect.TypeOf 能获取类型信息
    reflect.ValueOf 能获取数据的运行时表示

  • 两个类型
    reflect.Type 和 reflect.Value,reflect.Type 是一个接口而 reflect.Value 是一个结构体。

type Type interface {
    // Methods applicable to all types.

    Method(int) Method

    MethodByName(string) (Method, bool)

    NumMethod() int

    Name() string

    Kind() Kind

    // Size returns the number of bytes needed to store
    // a value of the given type; it is analogous to unsafe.Sizeof.
    Size() uintptr

    ……
    ……
}

type Value struct {
    //Value的类型
    typ *rtype
    ptr unsafe.Pointer
    flag
}

三大法则

  • 第一法则(从 interface{} 变量可以反射出反射对象)
    基本类型 int 会转换成 interface{} 类型,这也就是为什么第一条法则是从接口到反射对象。

  • 第二法则(从反射对象可以获取 interface{} 变量)
    将反射对象还原成接口类型的变量,采用 reflect.Value.Interface,注意需要显示转换

v := reflect.ValueOf(1)
v.Interface().(int)
  • 第三法则(要修改反射对象,其值必须可设置)
func main() {
    i := 1
    v := reflect.ValueOf(&i)
    v.Elem().SetInt(10)
    fmt.Println(i)
}

调用 reflect.ValueOf 获取变量指针;
调用 reflect.Value.Elem 获取指针指向的变量;
调用 reflect.Value.SetInt 更新变量的值:

使用场景

  • 处理未知类型的数据
  • 实现通用的数据处理逻辑
  • 实现序列化和反序列化

注意

反射的使用会带来一定的性能损耗,因此在性能要求较高的场景下,应慎重使用反射。同时,反射的代码可读性较低,容易引入错误,因此应尽量避免滥用反射。

方法使用

判定及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

反射值对象的判定及获取元素的方法

方法名 备注 返回值
Elem() 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回 nil 的 Value Value
Addr() 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 Value
CanAddr() 表示值是否可寻址 bool
CanSet() 返回值能否被修改。要求值可寻址且是导出的字段 bool

使用 reflect.Value 修改值的相关方法如下表所示。

反射值对象修改值的方法

方法名 备注
Set(x Value) 将值设置为传入的反射值对象的值。
SetInt(x int64) 使用 int64 设置值。当值的类型不是 int、int8、int16、int32、int64 时会发生宕机。
SetUint(x uint64) 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机。
SetFloat(x float64) 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机。
SetBool(x bool) 使用 bool 设置值。当值的类型不是 bool 时会发生宕机。
SetBytes(x []byte) 设置字节数组 []byte 值。当值的类型不是 []byte 时会发生宕机。
SetString(x string) 设置字符串值。当值的类型不是 string 时会发生宕机。

基本使用

package main

import (
    "fmt"
    "reflect"
)

func main() {

    var a int = 3

    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)

    // 根据反射类型对象创建类型实例
    aIns := reflect.New(typeOfA)

    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}

通过反射调用函数

type node struct {
    name string
    val  string
}

func (t node) Print() {
    fmt.Printf("output node val is %s\n", t.val)
}

func main() {
    var obj = node{
        name: "根",
        val:  "1",
    }
    typ := reflect.TypeOf(obj)
    fmt.Println(typ.Kind())

    va := reflect.ValueOf(obj)

    method := va.MethodByName("Print") //注意此处要大写开头才能导出字段
    // 判断方法是否存在
    if method.IsValid() {
        // 调用方法,传入空参数
        method.Call(nil)
    }
}

引用

http://c.biancheng.net/view/116.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容