首页 > 其他分享 >深入探究 Golang 反射:功能与原理及应用

深入探究 Golang 反射:功能与原理及应用

时间:2024-07-21 16:19:03浏览次数:15  
标签:反射 json Value Golang 探究 reflect value func interface

Hi 亲爱的朋友们,我是 k 哥。今天,咱们来一同探讨下 Golang 反射。

Go 出于通用性的考量,提供了反射这一功能。借助反射功能,我们可以实现通用性更强的函数,传入任意的参数,在函数内通过反射动态调用参数对象的方法并访问它的属性。举例来说,下面的bridge接口为了支持灵活调用任意函数,在运行时根据传入参数funcPtr,通过反射动态调用funcPtr指向的具体函数。

func bridge(funcPtr interface{}, args ...interface{})

再如,ORM框架函数为了支持处理任意参数对象,在运行时根据传入的参数,通过反射动态对参数对象赋值。

type User struct {
        Name string
        Age  int32
}
user := User{}
db.FindOne(&user)

本文将深入探讨Golang反射包reflect的功能和原理。同时,我们学习某种东西,一方面是为了实践运用,另一方面则是出于功利性面试的目的。所以,本文还会为大家介绍反射的典型应用以及高频面试题。

1 关键功能

reflect包提供的功能比较多,但核心功能是把interface变量转化为反射类型对象reflect.Type和reflect.Value,并通过反射类型对象提供的功能,访问真实对象的方法和属性。本文只介绍3个核心功能,其它方法可看官方文档。

1.对象类型转换。通过TypeOf和ValueOf方法,可以将interface变量转化为反射类型对象Type和Value。通过Interface方法,可以将Value转换回interface变量。

type any = interface{}

// 获取反射对象reflect.Type
// TypeOf returns the reflection Type that represents the dynamic type of i. 
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type

// 获取反射对象reflect.Value
// ValueOf returns a new Value initialized to the concrete value stored in the interface i. 
// ValueOf(nil) returns the zero Value.
func ValueOf(i any) Value

// 反射对象转换回interface
func (v Value) Interface() (i any)

示例如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    age := 18
    fmt.Println("type: ", reflect.TypeOf(age)) // 输出type:  int
    value := reflect.ValueOf(age)
    fmt.Println("value: ", value) // 输出value:  18

    fmt.Println(value.Interface().(int)) // 输出18
}

2.变量值设置。通过reflect.Value的SetXX相关方法,可以设置真实变量的值。reflect.Value是通过reflect.ValueOf(x)获得的,只有当x是指针的时候,才可以通过reflec.Value修改实际变量x的值。

// Set assigns x to the value v. It panics if Value.CanSet returns false. 
// As in Go, x's value must be assignable to v's type and must not be derived from an unexported field.
func (v Value) Set(x Value)
func (v Value) SetInt(x int64)
...

// Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Pointer. It returns the zero Value if v is nil.
func (v Value) Elem() Value

示例如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    age := 18
    // 通过reflect.ValueOf获取age中的reflect.Value
    // 参数必须是指针才能修改其值
    pointerValue := reflect.ValueOf(&age)
    // Elem和Set方法结合,相当于给指针指向的变量赋值*p=值
    newValue := pointerValue.Elem()
    newValue.SetInt(28)
    fmt.Println(age) // 值被改变,输出28

    // reflect.ValueOf参数不是指针
    pointerValue = reflect.ValueOf(age)
    newValue = pointerValue.Elem() // 如果非指针,直接panic: reflect: call of reflect.Value.Elem on int Value
}

3.方法调用。Method和MethodByName可以获取到具体的方法,Call可以实现方法调用。

// Method returns a function value corresponding to v's i'th method. 
// The arguments to a Call on the returned function should not include a receiver; 
// the returned function will always use v as the receiver. Method panics if i is out of range or if v is a nil interface value.
func (v Value) Method(i int) Value

// MethodByName returns a function value corresponding to the method of v with the given name.
func (v Value) MethodByName(name string) Value

// Call calls the function v with the input arguments in. For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]). 
// Call panics if v's Kind is not Func. It returns the output results as Values
func (v Value) Call(in []Value) []Value

示例如下:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Age int
}

func (u User) ReflectCallFunc(name string) {
    fmt.Printf("age %d ,name %+v\n", u.Age, name)
}

func main() {
    user := User{18}

    // 1. 通过reflect.ValueOf(interface)来获取到reflect.Value
    getValue := reflect.ValueOf(user)

    methodValue := getValue.MethodByName("ReflectCallFunc")
    args := []reflect.Value{reflect.ValueOf("k哥")}
    // 2. 通过Call调用方法
    methodValue.Call(args) // 输出age 18 ,name k哥
}

2 原理

Go语言反射是建立在Go类型系统和interface设计之上的,因此在聊reflect包原理之前,不得不提及Go的类型和interface底层设计。

2.1 静态类型和动态类型

在Go中,每个变量都会在编译时确定一个静态类型。所谓静态类型(static type),就是变量声明时候的类型。比如下面的变量i,静态类型是interface

var i interface{}

所谓动态类型(concrete type,也叫具体类型),是指程序运行时系统才能看见的,变量的真实类型。比如下面的变量i,静态类型是interface,但真实类型是int

var i interface{}   

i = 18 

2.2 interface底层设计

对于任意一个静态类型是interface的变量,Go运行时都会存储变量的值和动态类型。比如下面的变量age,会存储值和动态类型(18, int)

var age interface{}
age = 18

2.3 reflect原理

reflect是基于interface实现的。通过interface底层数据结构的动态类型和数据,构造反射对象。

reflect.TypeOf获取interface底层的动态类型,从而构造出reflect.Type对象。通过Type,可以获取变量包含的方法、字段等信息。

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i)) // eface为interface底层结构
    return toType(eface.typ) // eface.typ就是interface底层的动态类型
}

func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t
}

reflect.ValueOf获取interface底层的Type和数据,封装成reflect.Value对象。

type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype // 动态类型

    // Pointer-valued data or, if flagIndir is set, pointer to data.
    // Valid when either flagIndir is set or typ.pointers() is true.
    ptr unsafe.Pointer // 数据指针

    // flag holds metadata about the value.
    // The lowest bits are flag bits:
    //  - flagStickyRO: obtained via unexported not embedded field, so read-only
    //  - flagEmbedRO: obtained via unexported embedded field, so read-only
    //  - flagIndir: val holds a pointer to the data
    //  - flagAddr: v.CanAddr is true (implies flagIndir)
    //  - flagMethod: v is a method value.
    // The next five bits give the Kind of the value.
    // This repeats typ.Kind() except for method values.
    // The remaining 23+ bits give a method number for method values.
    // If flag.kind() != Func, code can assume that flagMethod is unset.
    // If ifaceIndir(typ), code can assume that flagIndir is set.
    flag // 标记位,用于标记此value是否是方法、是否是指针等

}

type flag uintptr

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    return unpackEface(i)
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
    // interface底层结构
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    // 动态类型
    t := e.typ
    if t == nil {
        return Value{}
    }
    // 标记位,用于标记此value是否是方法、是否是指针等
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f} // t为类型,e.word为数据,
}

3 应用

工作中,反射常见应用场景有以下两种:

1.不知道接口调用哪个函数,根据传入参数在运行时通过反射调用。例如以下这种桥接模式:

package main

import (
    "fmt"
    "reflect"
)

// 函数内通过反射调用funcPtr
func bridge(funcPtr interface{}, args ...interface{}) {
    n := len(args)
    inValue := make([]reflect.Value, n)
    for i := 0; i < n; i++ {
        inValue[i] = reflect.ValueOf(args[i])
    }
    function := reflect.ValueOf(funcPtr)
    function.Call(inValue)
}

func call1(v1 int, v2 int) {
    fmt.Println(v1, v2)
}
func call2(v1 int, v2 int, s string) {
    fmt.Println(v1, v2, s)
}
func main() {
    bridge(call1, 1, 2)         // 输出1 2
    bridge(call2, 1, 2, "test") // 输出1 2 test
}

2.不知道传入函数的参数类型,函数需要在运行时处理任意参数对象,这种需要对结构体对象反射。典型应用场景是ORM,orm示例如下:

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string
    Age  int32
}

func FindOne(x interface{}) {
    sv := reflect.ValueOf(x)
    sv = sv.Elem()
    // 对于orm,改成从db里查出来再通过反射设置进去
    sv.FieldByName("Name").SetString("k哥")
    sv.FieldByName("Age").SetInt(18)
}

func main() {
    user := &User{}
    FindOne(user)
    fmt.Println(*user) // 输出 {k哥 18}
}

4 高频面试题

1.reflect(反射包)如何获取字段 tag?

通过反射包获取tag。步骤如下:

  1. 通过reflect.TypeOf生成反射对象reflect.Type

  2. 通过reflect.Type获取Field

  3. 通过Field访问tag

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"name" otherTag:"name"`
    age  string `json:"age"`
}

func main() {
    user := User{}
    userType := reflect.TypeOf(user)
    field := userType.Field(0)
    fmt.Println(field.Tag.Get("json"), field.Tag.Get("otherTag")) // 输出name name

    field = userType.Field(1)
    fmt.Println(field.Tag.Get("json")) // 输出age
}


2.为什么 json 包不能导出私有变量的 tag?

从1中的例子中可知,反射可以访问私有变量age的tag。json包之所以不能导出私有变量,是因为json包的实现,将私有变量的tag跳过了。

func typeFields(t reflect.Type) structFields {
    // Scan f.typ for fields to include.
    for i := 0; i < f.typ.NumField(); i++ {
        sf := f.typ.Field(i)
        // 非导出成员(私有变量),忽略tag
        if !sf.IsExported() {
            // Ignore unexported non-embedded fields.
            continue
        }
        tag := sf.Tag.Get("json")
        if tag == "-" {
            continue
        }          
    }
}

3.json包里使用的时候,结构体里的变量不加tag能不能正常转成json里的字段?

  1. 如果是私有成员,不能转,因为json包会忽略私有成员的tag信息。比如下面的demo中,User结构体中的a和b都不能json序列化。

  2. 如果是公有成员。

  • 不加tag,可以正常转为json里的字段,json的key跟结构体内字段名一致。比如下面的demo,User中的C序列化后,key和结构体字段名保持一致是C。
  • 加了tag,从struct转json的时候,json的key跟tag的值一致。比如下面的demo,User中的D序列化后是d。
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    a string // 小写无tag
    b string `json:"b"` //小写+tag
    C string //大写无tag
    D string `json:"d"` //大写+tag
}

func main() {
    u := User{
        a: "1",
        b: "2",
        C: "3",
        D: "4",
    }
    fmt.Printf("%+v\n", u) // 输出{a:1 b:2 C:3 D:4}
    jsonInfo, _ := json.Marshal(u)
    fmt.Printf("%+v\n", string(jsonInfo)) // 输出{"C":"3","d":"4"}
}

标签:反射,json,Value,Golang,探究,reflect,value,func,interface
From: https://www.cnblogs.com/killianxu/p/18314594

相关文章

  • Java 语言及其常用集合类的操作,以及反射机制与注解
    目录一、Java语言概述二、Java集合框架ArrayList操作示例:HashMap操作示例:三、反射机制1.反射的示例五、总结Java是一种广泛使用的高级编程语言,因其平台独立性、简洁性及丰富的API而备受开发者青睐。一、Java语言概述 Java语言由JamesGosling等人......
  • Java反射
    java是面向对象的编程语言,对象,是一种具体的概念类:类是对具有相同特征或属性,具有相同行为能力的一类事物的描述或称呼对象:对象是这一类事物带有属性值,具有具体行为的个体或实例面向对象编程的语言packagetest;//定义一个类publicclassPhone{//添加一些属性......
  • 后仿真中《SDF反标必懂连载篇》之 探究 SDF延迟精度 与 timescale 精度问题
    目录一 SDF文件中的延迟数据二 设计文件中的timescale指令三 SDF精度和timescale之间的关系【例子1】【例子2】 【例子3】 【例子4】 本篇文章,同样属于后仿真中的SDF反标系列文章内容之一。今天,将前仿真中的timescale和后仿真中timescale+sdf延迟......
  • 0199-漫反射和伽马校正
    环境Time2022-11-16WSL-Ubuntu22.04Rust1.65.0前言说明参考:https://raytracing.github.io/books/RayTracingInOneWeekend.html目标对物体上的光线进行漫反射,然后增加伽马校正。颜色显示函数pubfnformat_str(&self,samples:f64)->String{letir=(256.......
  • 为什么反射慢?
    反射机制就是通过字节码文件对象获取成员变量、成员方法和构造方法,然后进一步获取它们的具体信息,如名字、修饰符、类型等。反射机制的性能较低有很多原因,这里详细总结以下4点原因:(1)JIT优化受限:JIT编译器的优化是基于静态分析和预测的。反射是一种在运行时动态解析类型信息的机......
  • 反射快速入门
    反射就是通过字节码文件获取类的成员变量、构造方法和成员方法的所有信息。利用反射,我们可以获取成员变量的修饰符、名字、类型、取值。我们可以获取构造方法的名字、形参,并利用通过反射获取的构造方法创建对象。我们可以获取成员方法的修饰符、名字、形参、返回值、抛出的异常、......
  • Golang的KisFlow流式计算框架概述
    1.1为什么需要KisFlow一些大型toB企业级的项目,需要大量的业务数据,多数的数据需要流式实时计算的能力,但是很多公司还不足以承担一个数仓类似,Flink+Hadoop/HBase等等。但是业务数据的实时计算需求依然存在,所以大多数的企业依然会让业务工程师来消化这些业务数据计算的工作......
  • 农村高中生源转型期提升学生二次函数建模能力的课堂探究
       数学建模能力的提升建立在学生具备数学建模思维与思想的基础上,亲自对数学建模过程形成深刻认知,并且通过具体的问题分析来获取必要的数学建模经验与技巧等。因此,在开展数学教学期间,教师要注意有计划、有目的地结合一些实际社会问题,引导高中生仔细地观察和分析问题,使他们在......
  • golang 无缓冲区通道的 range 操作
     对一个无缓冲通道(chan)进行range操作意味着想要迭代并消费通道中的所有值。但是,由于无缓冲通道的特性,这种操作具有特定的行为和潜在的副作用,主要体现在以下几点: 阻塞行为:当你开始对无缓冲通道进行range操作时,每次循环迭代都会尝试从通道接收一个值。如果通......
  • 反射工具类
    InvokeUtils代码如下:packagecom.ksource.utils;importorg.apache.commons.collections4.MultiValuedMap;importjava.lang.reflect.Method;importjava.util.Collection;/***@Authordxy*@Date2024/7/1617:54*@Description*/publicclassInvokeUtils{......