Go语言学习教程之反射的示例详解

 更新时间:2022年09月28日 09:53:41   作者:任沫  
这篇文章主要通过记录对reflect包的简单使用,来对反射有一定的了解。文中的示例代码讲解详细,对我们学习Go语言有一定帮助,需要的可以参考一下

介绍

reflect包实现运行时反射,允许一个程序操作任何类型的对象。典型的使用是:取静态类型interface{}的值,通过调用TypeOf获取它的动态类型信息,调用ValueOf会返回一个表示运行时数据的一个值。本文通过记录对reflect包的简单使用,来对反射有一定的了解。本文使用的Go版本:

$ go version
go version go1.18 darwin/amd64

在了解反射之前,先了解以下几个概念:

  • 静态类型:每个变量都有一个静态类型,这个类型是在编译时(compile time)就已知且固定的。
  • 动态类型:接口类型的变量还有一个动态类型,是在运行时(run time)分配给变量的值的一个非接口类型。(除非分配给变量的值是nil,因为nil没有类型)。
  • 空接口类型interface{} (别名any)表示空的方法集,它可以是任何值的类型,因为任何值都满足有0或多个方法(有0个方法一定是任何值的子集)。
  • 一个接口类型的变量存储一对内容:分配给变量的具体的值,以及该值的类型描述符。可以示意性地表示为(value, type)对,这里的type是具体的类型,而不是接口类型。

反射的规律

1. 从接口值到反射对象的反射

在基本层面,反射只是检测存储在接口变量中的(value, type)对的一种机制。

可以使用reflect包的 reflect.ValueOfreflect.TypeOf方法,获取接口变量值中的(value, type)对,类型分别为reflect.Valuereflect.Type

(1)TypeOf方法:

func TypeOf(i any) Type

TypeOf返回表示i的动态类型的反射Type。如果inil,那么返回nil

(2)ValueOf方法:

func ValueOf(i any) Value

ValueOf返回一个新的Value,初始化为存储在接口i中的具体值。ValueOf(nil) 会返回零Value。这个零Value是反射对象中表示没有值的Value

var a interface{} = 1
var b interface{} = 1.11
var c string = "aaa"

// 将接口类型的变量运行时存储的具体的值和类型显示地获取到
fmt.Println("type:", reflect.TypeOf(a))   // type: int
fmt.Println("value:", reflect.ValueOf(a)) // value: 1

fmt.Println("type:", reflect.TypeOf(nil))   // type: <nil>
fmt.Println("value:", reflect.ValueOf(nil)) // value: <invalid reflect.Value>

fmt.Println("type:", reflect.TypeOf(b))   // type: float64
fmt.Println("value:", reflect.ValueOf(b)) // value: 1.11

fmt.Println("type:", reflect.TypeOf(c))   // type: string
fmt.Println("value:", reflect.ValueOf(c)) // value: aaa

reflect.Value 的 Type方法,返回一个 reflect.Value的类型。

reflect.ValueString方法,将reflect.Value的底层值作为字符串返回。

fmt.Printf使用了反射:

  • %T使用reflect.TypeOf,拿到变量的动态类型。
  • %v深入到 reflect.Value内部拿到变量具体的值。
var a interface{} = 1

fmt.Println("type:", reflect.ValueOf(a).Type())     // type: int
fmt.Println("string:", reflect.ValueOf(a).String()) // string: <int Value>
fmt.Printf("type: %T \n", a)                        // type: int
fmt.Printf("string: %v \n", a)                      // string: 1

Type 和 Value都有一个 Kind 方法,返回一个表示存储的项的类型的常量。 比如UintFloat64Slice等。

Value的类似IntFloat这种名称的方法能够获取存储在内部的值。

Value 的“getter”(取值) 和 “setter”(设置值)会对能保存该值的最大类型进行操作。比如对于所有有符号整数,都是int64。

var a interface{} = 1
var b interface{} = 1.11

reflectA := reflect.ValueOf(a)
fmt.Println("kind: ", reflectA.Kind()) // kind:  int

reflectIntA := reflectA.Int()               // 返回的是 能存储有符号整数的最大类型 的值
reflectFloatB := reflect.ValueOf(b).Float() // 返回的是 能存储浮点数的最大类型 的值

var a1 int64 = reflectIntA
var b1 float64 = reflectFloatB

// var a2 int32 = reflectIntA // 会报错:cannot use reflectIntA (variable of type int64) as int32 value in variable 
// var b2 float32 = reflectFloatB // 会报错:cannot use reflectFloatB (variable of type float64) as float32 value in variable 
fmt.Println("a1: ", a1, "b1: ", b1) // a1:  1 b1:  1.11

2. 从反射对象到接口值的反射

像物理反射一样,Go 中的反射产生了它自己的逆。可以使用reflect.ValueInterface方法还原一个接口值。Interface() 将类型和值信息打包回一个接口表示。

Interface方法:

func (v Value) Interface() (i any)

Interfacev的当前值作为一个interface{}返回。它等同于:

var i interface{} = (v 的底层值)

练习代码:

var a interface{} = 1

reflectA := reflect.ValueOf(a)

var a3 interface{} = reflectA.Interface()
var a4 int = reflectA.Interface().(int) // 使用接口值的类型断言

fmt.Println(a3, a4) // 1 1 ,因为fmt.Println接收接口类型interface{}的参数,使用 reflect.Value 拿到具体的值,所以打印出运行时的具体结果

3. 要修改反射对象,该值一定是可设置的

可设置性是reflect.Value的一个属性,表示一个反射对象可以修改用于创建该反射对象的实际存储。可设置性可以通过CanSet方法获得。如果对不可设置的reflect.Value调用Set方法,就会报错。

使用reflect.Value 类型的Elem方法能通过指针间接寻址得到一个可设置性为真的reflect.Value

var d float64 = 2.222

fmt.Println(reflect.ValueOf(d).CanSet()) // false

reflectD := reflect.ValueOf(&d).Elem()
fmt.Println(reflectD.CanSet()) // true
reflectD.SetFloat(3.33)

fmt.Println(d, reflectD) // 3.33 3.33

reflect.ValueOf(d) 是通过复制d中的内容得到的reflect.Value类型的值,它复制的内容存放的内存地址 和d的值存放的内存地址是不同的。所以不能通过它来修改d中原本存储的内容。

上面的代码中可以看到,调用SetFloat(3.33)之后,反射对象reflectD和创建该反射对象的d都发生了改变。

使用反射修改结构体的字段:

type T struct {
    A int
    B string
}
t := T{111, "xxx"}
s := reflect.ValueOf(&t).Elem()

typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ { // NumField 返回结构体中的字段数量
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n",
        i,
        typeOfT.Field(i).Name, // 获取第i个字段的名称
        f.Type(),              // 获取第i个字段的类型
        f.Interface(),         // 将第i个字段转换回接口类型的值
    )
    // 0: A int = 111
    // 1: B string = xxx
}

s.Field(0).SetInt(222)      // 设置结构体的第一个字段的值
s.Field(1).SetString("yyy") // 设置结构体的第二个字段的值
fmt.Println(t)              // {222 yyy}

上述练习代码都在一个reflect.go文件中,练习时在终端执行go run reflect.go运行该文件。

以上就是Go语言学习教程之反射的示例详解的详细内容,更多关于Go语言 反射的资料请关注脚本之家其它相关文章!

相关文章

  • Go1.21新增内置函数(built-in functions)详解

    Go1.21新增内置函数(built-in functions)详解

    Go 1.21新增的内置函数分别是 min、max 和 clear,这篇文章主要带大家一起了解一下这几个函数的用途和使用示例,感兴趣的小伙伴可以学习一下
    2023-08-08
  • 浅谈Go语言并发机制

    浅谈Go语言并发机制

    这篇文章主要介绍了浅谈Go语言并发机制,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • GoFrame框架gredis优雅的取值和类型转换

    GoFrame框架gredis优雅的取值和类型转换

    这篇文章主要为大家介绍了GoFrame框架gredis优雅的取值和类型转换,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • golang之数据校验的实现代码示例

    golang之数据校验的实现代码示例

    这篇文章主要介绍了golang之数据校检的实现代码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • 详解Golang如何优雅的终止一个服务

    详解Golang如何优雅的终止一个服务

    后端服务通常会需要创建子协程来进行相应的作业,但进程接受到终止信号或正常结束时,并没有判断或等待子协程执行结束,下面这篇文章主要给大家介绍了关于Golang如何优雅的终止一个服务的相关资料,需要的朋友可以参考下
    2022-03-03
  • go日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Go 1.18新特性之泛型的全面讲解

    Go 1.18新特性之泛型的全面讲解

    本文力求能让未接触过泛型编程的人也能较好理解Go的泛型,所以行文可能略显啰嗦。但是请相信我,看完这篇文章你能获得对Go泛型非常全面的了解
    2023-03-03
  • Golang实现反向代理的示例代码

    Golang实现反向代理的示例代码

    这篇文章主要为大家学习介绍了如何利用Golang实现反向代理,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-07-07
  • Golang创建构造函数的方法超详细讲解

    Golang创建构造函数的方法超详细讲解

    构造器一般面向对象语言的典型特性,用于初始化变量。Go语言没有任何具体构造器,但我们能使用该特性去初始化变量。本文介绍不同类型构造器的差异及其应用场景
    2023-01-01
  • go语言 xorm框架 postgresql 的用法及详细注解

    go语言 xorm框架 postgresql 的用法及详细注解

    这篇文章主要介绍了go语言 xorm框架 postgresql 的用法及详细注解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12

最新评论