前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >22.Go语言-反射

22.Go语言-反射

原创
作者头像
面向加薪学习
发布2022-09-04 11:30:02
2730
发布2022-09-04 11:30:02
举报
文章被收录于专栏:面向加薪学习面向加薪学习

第 22 章节 反射

22.1 reflect 包

Go 语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法,而不需要在编译时就知道这些变量的具体类型。这种机制被称为 反射

反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。

在 Go 中 reflect 包实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。

22.1.1 reflect.Type

reflect.Type 表示 interface{} 的具体类型。reflect.TypeOf() 方法返回 reflect.Type

像我们之前讲过的空接口参数的函数,可以通过类型断言来判断传入变量的类型,也可以借助反射来确定传入变量的类型。

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func reflectType(x interface{}) {
    obj := reflect.TypeOf(x)
    fmt.Println(obj)
}

func main() {
    var a int64 = 123
    reflectType(a)
    var b string = "从0到Go语言微服务架构师"
    reflectType(b)
}

22.1.2 reflect.Value

reflect.Value 表示 interface{} 的具体值。reflect.ValueOf() 方法返回 reflect.Value

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func reflectType(x interface{}) {
    typeX := reflect.TypeOf(x)
    valueX := reflect.ValueOf(x)
    fmt.Println(typeX)
    fmt.Println(valueX)
}

func main() {
    var a int64 = 123
    reflectType(a)
    var b string = "从0到Go语言微服务架构师"
    reflectType(b)
}

22.1.3 relfect.Kind

relfect.Kind 表示的是种类。在使用反射时,需要理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。

Go 语言程序中的类型(Type)指的是系统原生数据类型,如 intstringboolfloat32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

代码语言:go
复制
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

通过下面这个程序,相信你会很容易明白这两者的区别:

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func reflectType(x interface{}) {
    typeX := reflect.TypeOf(x)
    fmt.Println(typeX.Kind()) // struct
    fmt.Println(typeX)        // main.book
}

type book struct {
}

func main() {
    var b book
    reflectType(b)
}

22.1.4 relfect.NumField()

relfect.NumField() 方法返回结构体中字段的数量。

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func reflectNumField(x interface{}) {
    // 检查 x 的类别是 struct
    if reflect.ValueOf(x).Kind() == reflect.Struct {
        v := reflect.ValueOf(x)
        fmt.Println("Number of fields", v.NumField())
    }
}

type book struct {
    name string
    spend  int
}

func main() {
    var b book
    reflectNumField(b)
}

22.1.5 relfect.Field()

relfect.Field(i int) 方法返回字段 ireflect.Value

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func reflectNumField(x interface{}) {
    // 检查 x 的类别是 struct
    if reflect.ValueOf(x).Kind() == reflect.Struct {
        v := reflect.ValueOf(x)
        fmt.Println("Number of fields", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
        }
    }
}

type book struct {
    name string
    spend  int
}

func main() {
    var b = book{"《Go语言极简一本通》", 8}
    reflectNumField(a)
}

22.2 反射的三大定律

之前在 静态类型与动态类型章节中讲过,一个接口变量,实际上都是由一 pair 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界(反射前环境)里,type 和 value 是合并在一起组成接口变量的。

而在反射的世界(反射后的环境)里,type 和 data 却是分开的,他们分别由 reflect.Typereflect.Value 来表现。

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.

接下来我们就来讲一讲反射三大定律。

22.2.1 反射第一定律

Reflection goes from interface value to reflection object.

反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”。

这里反射类型指 reflect.Typereflect.Value

通过之前我们讲过的 reflect.TypeOf() 方法和 reflect.ValueOf() 方法可以分别获得接口值的类型和接口值的值。这两个方法返回的对象,我们称之为反射对象。

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a interface{} = 3.14
    fmt.Printf("接口变量的类型为 %T ,值为 %v\n", a, a)
    t := reflect.TypeOf(a)
    v := reflect.ValueOf(a)
    fmt.Printf("从接口变量到反射对象:Type对象类型为 %T\n", t)
    fmt.Printf("从接口变量到反射对象:Value对象类型为 %T\n", v)
}

可以看到,使用 reflect.TypeOf()reflect.ValueOf() 方法完成了从接口类型变量到反射对象的转换。在这里说接口类型是因为 TypeOfValueOf 两个函数接收的是 interface{} 空接口类型, Go 语言函数都是值传递,会将类型隐式转换成接口类型。

22.2.2 反射第二定律

Reflection goes from reflection object to interface value.

反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”

第二定律刚好和第一定律相反,第一定律讲的是从接口变量到反射对象的转换,而第二定律讲的是从反射对象到接口变量的转换。

一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 typevalue 信息打包并填充到一个接口变量中,然后返回。

其函数声明如下:

代码语言:go
复制
// Interface returns v's current value as an interface{}.
// It is equivalent to:
//    var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
    return valueInterface(v, true)
}

最后转换后的对象静态类型为 interface{},我们可以使用类型断言转换为原始类型。

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a interface{} = 3.14

    fmt.Printf("接口变量的类型为 %T ,值为 %v\n", a, a)

    t := reflect.TypeOf(a)
    v := reflect.ValueOf(a)

    // 反射第一定律
    fmt.Printf("从接口变量到反射对象:Type对象类型为 %T\n", t)
    fmt.Printf("从接口变量到反射对象:Value对象类型为 %T\n", v)

    // 反射第二定律
    i := v.Interface()
    fmt.Printf("从反射对象到接口变量:对象类型为 %T,值为 %v\n", i, i)
    // 使用类型断言进行转换
    x := v.Interface().(float64)
    fmt.Printf("x 类型为 %T,值为 %v\n", x, x)
}

22.2.3 反射第三定律

To modify a reflection object, the value must be settable.

反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”

我们首先来看一看下面这段代码:

代码语言:go
复制
package main

import "reflect"

func main() {
    var a float64 = 3.14
    v := reflect.ValueOf(a)
    v.SetFloat(2.1)
}

运行该代码段将会抛出异常:

代码语言:go
复制
panic: reflect: reflect.Value.SetFloat using unaddressable value

这里你可能会疑惑,为什么这里会抛出寻址的异常,其实是因为这里的变量 v 是“不可写的”。settable(“可写性”)是反射类型变量的一个属性,但也不是说所有的反射类型变量都有这个属性。

要想知道一个 reflect.Value 类型变量的“可写性”,我们可以使用 CanSet 方法来进行检查:

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a float64 = 3.14
    v := reflect.ValueOf(a)
    fmt.Println("是否可写:", v.CanSet())
}

可以看到,我们这个变量 v 是不可写的。对于一个不可写的变量,使用 Set 方法会报错。这里实质上还是 Go 语言里的函数都是值传递问题,想象一下这里传递给 reflect.ValueOf 函数的是变量 a 的一个拷贝,而非 a 本身,所以如果对反射对象进行更新,其原始变量 a 根本不会受到影响,所以是不合法的,“可写性”就是为了避免这个问题而设计出来的。

所以,要让反射对象具备“可写性”,一定要注意创建反射对象时要传入变量的指针,于是乎我们修改代码如下:

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a float64 = 3.14
    v := reflect.ValueOf(&a)
    fmt.Println("是否可写:", v.CanSet())
}

但运行该程序还是会输出不可写,因为事实上我们这里要修改的是该指针指向的数据,使用还要使用 Value 类型的 Elem() 方法,对指针进行“解引用”,该方法返回指针指向的数据。

代码语言:go
复制
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a float64 = 3.14
    v := reflect.ValueOf(&a).Elem()
    fmt.Println("是否可写:", v.CanSet())

    v.SetFloat(2)
    fmt.Println(v)
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第 22 章节 反射
    • 22.1 reflect 包
      • 22.1.1 reflect.Type
      • 22.1.2 reflect.Value
      • 22.1.3 relfect.Kind
      • 22.1.4 relfect.NumField()
      • 22.1.5 relfect.Field()
    • 22.2 反射的三大定律
      • 22.2.1 反射第一定律
      • 22.2.2 反射第二定律
      • 22.2.3 反射第三定律
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档