美文网首页
golang 中的反射

golang 中的反射

作者: 癞痢头 | 来源:发表于2023-04-19 10:54 被阅读0次

golang 中反射 【反射第一定律】

  • 反射是从接口值到反射对象 【Reflection goes from interface value to reflection object.】

简单来说,反射只是一种检查存储在接口变量中的类型和值对的机制。

首先,我们需要了解包reflect中的两种类型:类型(value)和值(Type)。这两个类型给予了对接口变量内容的访问,两个简单的函数,称为reflect.TypeOf和reflect.ValueOf,检索接口值中的reflect.Type和reflect.Value片段。(而且,从reflect.Value很容易到达对应的reflect.Type,但是现在让我们把Value和Type的概念分开。)

让我们从TypeOf开始:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}


此程序打印

type: float64

您可能会奇怪接口在哪里,因为程序看起来像是在向reflect. TypeOf传递float 64变量x,而不是接口值。但它就在那里;正如godoc报告的那样,reflect.TypeOf的签名包含一个空接口:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

当调用reflect.TypeOf(x)时,x首先存储在一个空接口,这个空接口会作为后续处理的参数;reflect.TypeOf解包该空接口以恢复类型信息。

当然,reflect.ValueOf函数会恢复值:

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())

打印结果:

value: <float64 Value>

(在此,我们显式调用String方法,因为默认情况下,fmt包会深入到reflect.value中以显示其中的具体值。String方法则不会。)

reflect.type和reflect.value都有很多方法让我们检查和操作接口。一个重点是:Value有一个Type方法,返回reflect.Value的Type值。另一重点是Type和Value都有一个Kind方法,该方法返回一个常量,指示存储了哪种类型的项:Uint、Float64、Slice等等。Value上的Int和Float等方法也让我们获取存储在内部的值(如int64和float64):

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

打印结果:

type: float64
kind is float64: true
value: 3.4

还有像SetInt和SetFloat这样的方法,但是要使用它们,我们需要理解可设置性,这是反射第三定律的主题,下面将讨论。

反射库有几个属性需要特别特别指出。首先,为了保持API的简单性,Value的“getter”和“setter”方法对可以保存值的最大类型进行操作:例如,所有有符号整数都是int64。也就是说,Value的Int方法返回一个int64,而SetInt值采用一个int64;可能需要转换为所涉及的实际类型:

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())                            // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())                                       // v.Uint returns a uint64.

第二个属性是反射对象的Kind描述基础类型,而不是静态类型。如果反射对象包含用户定义的整数类型的值,如

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

上面的代码中,v的Kind仍然是reflect.Int,即使x的静态类型是MyInt,而不是int。换句话说,Kind不能区分int和MyInt,即使Type可以。

反射第二定律

  • 反射从反射对象到接口值。 【Reflection goes from reflection object to interface value.】

就像物理反射一样,Go语言中的反射也会产生自逆现象。

给定一个reflect.Value,我们可以使用Interface方法恢复一个接口值;实际上,该方法将类型和值信息打包回接口表示,并返回结果:

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

打印由反射对象v表示的float64值:

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

打印可以更简洁,因为fmt.Println、fmt.Printf等的参数都作为空接口值传递,然后由fmt包在内部解包,就像我们在前面的示例中所做的那样。因此,正确打印reflect.Value的内容所需要做的就是将Interface方法的结果传递给格式化打印例程:

fmt.Println(v.Interface())

从本文是第一次编写后,对fmt包进行了更改,以便它自动解包reflect.value,就像下面这样:

fmt.Println(v)

但为了清楚起见,在这里保留.Interface()调用。

因为我们的值是float64,如果需要,我们甚至可以使用浮点格式:

fmt.Printf("value is %7.1e\n", v.Interface())

打印结果:

3.4e+00

因此,不需要类型断言v.Interface()的结果到float64;空的接口值里面有具体值的类型信息,Printf将直接恢复它。

简而言之,Interface方法是ValueOf函数的逆函数,只是它的结果总是静态类型interface{}。

重申:反射从接口值到反射对象,然后再返回。

反射第三定律

  • 若要修改反射对象,该值必须是可设置的

第三定律是最微妙和令人困惑的,但如果我们从第一原理开始,它就很容易理解。

下面是一些不工作的代码,但值得研究:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

如果您运行此代码,它将出现异常,并显示隐藏的消息:

panic: reflect.Value.SetFloat using unaddressable value

问题在于v是不可设置的,可设置性是反射值的一个属性,并非所有反射值都具有该属性。

Value的CanSet方法报告Value的可设置性;在我们例子中:

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())

打印:settability of v: false

由于它表示x,我们最终可以使用v.SetFloat来修改x的值:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

如预期的那样,打印为:

7.1
7.1

反射可能很难理解,但它确实在做语言所做的事情,尽管通过反射类型和值可以探视正在发生的事情。请记住,如果要修改反射值所代表的内容,需要他们是饮用类型。

Structs

在我们前面的例子中,v本身并不是一个指针,它只是从一个指针派生出来的。出现这种情况的常见方式是使用反射修改结构的字段。只要我们有结构的地址,我们就可以修改它的字段。

下面是一个简单的例子,它分析了一个结构值t。我们用结构体的地址创建反射对象,因为我们以后会想修改它。然后,我们将typeOfT设置为它的类型,并使用直接的方法调用迭代字段(有关详细信息,请参阅包反射)。请注意,我们从结构类型中提取字段的名称,但字段本身是常规的reflect.value对象。

type T struct {
    A int
    B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}

这个程序的输出是

0: A int = 23
1: B string = skidoo

这里顺便介绍了关于可设置性的另一点:T的字段名是大写的(导出),因为只有结构的导出字段是可设置的。

因为s包含一个可设置的反射对象,我们可以修改结构的字段。

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

结果是: t is now {77 Sunset Strip}

结论:

  1. 反射从接口到反射对象
  2. 反射从反射对象到接口
  3. 修改反射对象,它必须是可设置的

参考

相关文章

  • golang反射用法举例(注册插件)

    有关golang反射的内容,网上有大量讲述,请自行google——"golang反射三法则" 下面主要反射在实际中...

  • golang中的反射

    本文转载自https://github.com/KeKe-Li/For-learning-Go-Tutorial/...

  • Golang 反射实现依赖注入

    Golang 反射实现依赖注入 Coding/Golang #Golang #Golang/reflect 依赖注...

  • (转载)golang 中的反射

    我们定义的一个变量,不管是基本类型int,还是一个结构体Student,我们都可以通过reflect.TypeOf...

  • golang中的reflect(反射)

    一、概述 在golang中,reflect是一个比较高级的话题,本文将尽可能简单而又清楚的介绍相关内容。 本文将从...

  • golang记录

    获取本地IP 限制golang最大并发数 golang最快响应伪代码如下 反射修改对象属性 关于defer中坑的最...

  • 反射 reflection golang

    原文链接:反射reflection-GOLANG

  • 如何理解 Golang 中的反射

    欢迎访问博客原文:http://pengtuo.tech/golang/2019/09/23/golang-ref...

  • Golang 反射

    基本了解 在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个Struct 你想要一个值,你...

  • golang 反射

    反射机制是指在程序运行的时候动态地获取对象的属性后者调用对象方法的功能。golang 支持反射,原生的 json ...

网友评论

      本文标题:golang 中的反射

      本文链接:https://www.haomeiwen.com/subject/drfljdtx.html