您的位置:

Golang反射详解

一、反射基础

反射是指在运行时动态地获取变量的类型(type)和值(value),并且可以修改变量的值或调用其方法。在golang中,通过reflect包实现反射功能。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var num float64 = 1.234
    // 获取变量的类型和值
    value := reflect.ValueOf(num)
    kind := reflect.TypeOf(num).Kind()
    fmt.Println("type:", kind, "| value:", value)

    // 尝试修改变量的值
    // This will panic at runtime!
    value.SetFloat(3.1415926)
    fmt.Println("num after modify:", num)
}

运行上面示例代码,输出结果为:

type: float64 | value: 1.234
panic: reflect.Value.SetFloat using unaddressable value

可以看到,虽然可以获取变量num的类型和值,但是修改变量的值会引发panic异常,因为变量num的地址是不可寻址的(unaddressable)。

二、反射中的Kind和Type

反射中的Kind和Type是两个重要的概念。

Kind表示变量的基础类型,它是一个枚举类型(reflect.Kind),包括了所有基础类型及数组、结构体等复杂类型。

Type表示变量的静态类型,它描述的是变量的完整类型信息,包括类型名、包名、方法集等,并且与Kind不同,Type是可以寻址的。

可以通过reflect.Value.Interface()方法获取变量的静态类型。下面的例子程序演示了Kind和Type的使用方法。

package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    Name string
}

func main() {
    var num float64 = 1.234
    str := "string"
    arr := [...]int{1, 2, 3, 4, 5}
    s := MyStruct{"struct"}

    values := []interface{}{num, str, arr, &s}
    for _, value := range values {
        kind := reflect.ValueOf(value).Kind()
        tp := reflect.TypeOf(value)
        fmt.Printf("value :%v\t| kind: %v\t| type:%v\n", value, kind, tp)
    }
}

运行上面的代码,输出:

value :1.234  | kind: float64 | type:float64
value :string | kind: string  | type:string
value :[5]int | kind: array   | type:[5]int
value :&{struct} | kind:ptr  | type:*main.MyStruct

可以看到,不同类型变量的Kind和Type是不同的。

三、反射修改值

使用反射可以方便地获取已知类型变量的值和类型信息,同时也可以修改其值或调用方法。

在golang中,使用反射修改变量值时,必须满足以下几个条件:

  1. 变量必须是可寻址的(addressable)
  2. 变量必须是可修改的(settable)

下面的示例代码演示了如何使用反射修改变量值。由于在golang中,只有通过指针才能将变量的地址暴露出来并满足可修改性,因此示例代码中所有变量都用指针类型定义。

package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    Name string
}

func main() {
    num1 := 2
    s := MyStruct{"struct"}
    num2 := &num1
    p := &s

    values := []interface{}{num2, p}
    for _, value := range values {
        val := reflect.ValueOf(value).Elem()
        if val.CanSet() {
            switch val.Kind() {
            case reflect.Int:
                val.Set(reflect.ValueOf(10))
            case reflect.Ptr:
                elem := val.Elem()
                switch elem.Kind() {
                case reflect.Struct:
                    field := elem.FieldByName("Name")
                    if field.Kind() == reflect.String {
                        field.SetString("new name")
                    }
                }
            }
        }
        fmt.Println(value)
    }
}

运行上面的代码,输出:

10
&{new name}

可以看到,通过反射可以方便地修改变量的值。记住,要可修改,必须要可寻址。

四、反射获取方法和调用方法

使用反射可以很方便地获取变量的方法集并调用其方法。

在golang中,使用反射获取方法集时,需要使用reflect.Type来描述类型信息,并且仅通过类型信息可能不足以描述完整的方法集,因此反射还提供了reflect.ValueOf和reflect.MethodByName等函数来获取方法和调用。

下面示例代码演示了如何使用反射获取方法和调用方法。

package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    Name string
}

func (s *MyStruct) SetName(name string) {
    s.Name = name
}

func (s *MyStruct) GetName() string {
    return s.Name
}

func main() {
    s := MyStruct{"struct"}
    fmt.Println(s)

    val := reflect.ValueOf(&s)
    setValue := val.MethodByName("SetName")
    getName := val.MethodByName("GetName")
    args := []reflect.Value{reflect.ValueOf("new name")}
    setValue.Call(args)

    name := getName.Call([]reflect.Value{})
    fmt.Println(name[0].String())
    fmt.Println(s)
}

运行上面的代码,输出:

{struct}
new name
{new name}

可以看到,使用反射获取方法集并调用方法是非常方便的。

五、反射和接口的关系

golang中的接口类型(interface{})是非常重要的数据类型,所有自定义的接口类型都需要实现该类型,实现原理就是动态类型和动态值之间的关系。

使用反射可以很方便地操作接口类型的数据。下面的示例代码演示了如何使用反射获取接口类型的动态类型和值。

package main

import (
    "fmt"
    "reflect"
)

type MyInterface interface {
    SayHello(name string) string
}

type MyStruct struct {
    Name string
}

func (s *MyStruct) SayHello(name string) string {
    return fmt.Sprintf("Hello %s, I'm %s.", name, s.Name)
}

func main() {
    s := MyStruct{"struct"}
    var myInt MyInterface = &s

    // 获取接口的动态类型和值
    val := reflect.ValueOf(myInt).Elem()
    tp := reflect.TypeOf(myInt).Elem()

    method := val.MethodByName("SayHello")
    args := []reflect.Value{reflect.ValueOf("name")}
    result := method.Call(args)

    fmt.Println(tp.Name())
    fmt.Println(result[0].String())
}

运行上面的代码,输出:

MyInterface
Hello name, I'm struct.

可以看到,使用反射可以很方便地获取接口类型的信息,方法集和调用方法。