反射用途

反射在许多方面都非常有用,比如:

动态编程: 通过反射,你可以动态地创建对象,调用方法,甚至构建全新的类型。
框架与库开发: 很多流行的Go框架,如Gin、Beego等,都在内部使用反射来实现灵活和高度可定制的功能。
元编程: 你可以写出可以自我分析和自我修改的代码,这在配置管理、依赖注入等场景中尤为有用。

作者:techlead_krischang
链接:https://juejin.cn/post/7291094611484966970
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

反射与类型系统

反射紧密地与类型系统联系在一起。在静态类型语言(例如Go)中,每一个变量都有预先定义的类型,这些类型在编译期就确定。但反射允许你在运行时去查询和改变这些类型。

// 代码示例:查询变量的类型和值
import "reflect"

func main() {
var x int = 42
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("Type:", t)
fmt.Println("Value:", v)
}
// 输出: Type: int
// 输出: Value: 42

反射与接口

在Go中,接口(interface)和反射是紧密相关的。事实上,你可以认为接口是实现反射的“入口”。当你将一个具体类型的变量赋给一个接口变量时,这个接口变量内部存储了这个具体变量的类型信息和数据。


// 代码示例:接口与反射
type Any interface{}

func inspect(a Any) {
    t := reflect.TypeOf(a)
    v := reflect.ValueOf(a)
    fmt.Println("Type:", t, "Value:", v)
}

func main() {
    var x int = 10
    var y float64 = 20.0
    inspect(x) // Type: int Value: 10
    inspect(y) // Type: float64 Value: 20
}


package main

import (
	"fmt"
	"reflect"
)

type Human struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	var human Human = Human{
		Name: "meowrain",
		Age:  20,
	}
	tp := reflect.TypeOf(human)

	vp := reflect.ValueOf(human)
	fmt.Println(tp)
	fmt.Println(vp)

}
结果:
main.Human
{meowrain 20}

反射的分类

反射在Go中主要有两个方向:

类型反射(Type Reflection): 主要关注于程序运行时获取变量的类型信息。
值反射(Value Reflection): 主要关注于程序运行时获取或设置变量的值。

类型反射

在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空。
https://pkg.go.dev/reflect#Kind

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
	Pointer
	Slice
	String
	Struct
	UnsafePointer
)
package main

import (
	"fmt"
	"reflect"
)

type Human struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func inspector(data any) {
	//看看Kind()的区别
	t := reflect.TypeOf(data)
	fmt.Println("name:", t.Name())
	fmt.Println("kind:", t.Kind())

}

func main() {
	fmt.Println("==============传入结构体========================")
	var human Human = Human{
		Name: "meow",
		Age:  20,
	}
	inspector(human)
	fmt.Println("===================================================")
	fmt.Println()
	fmt.Println("===============传入结构体指针=======================")
	inspector(&human)
	fmt.Println("===================================================")
	fmt.Println()
	fmt.Println("=================传入字符串=====================")
	var a string = "hello world"
	inspector(a)
	fmt.Println("======================================")
	fmt.Println()
	fmt.Println("================传入字符串指针======================")
	inspector(&a)
	fmt.Println("======================================")
}


> ==============传入结构体========================
name: Human
kind: struct
===================================================

> ===============传入结构体指针=======================
name:
kind: ptr
===================================================

> =================传入字符串=====================
name: string
kind: string
======================================

> ================传入字符串指针======================
name:
kind: ptr
======================================

值反射

reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value与原始值之间可以互相转换。

reflect.Value类型提供的获取原始值的方法如下:

image


package main

import (
   "fmt"
   "reflect"
)

type Human struct {
   Name string `json:"name"`
   Age  int    `json:"age"`
}

func inspector(data any) {
   v := reflect.ValueOf(data)

   if v.Kind() == reflect.Struct {
   	fmt.Println("是结构体")
   } else if v.Kind() == reflect.Pointer {
   	ov := v.Elem()
   	if ov.Kind() == reflect.Struct {
   		fmt.Println("是结构体指针")
   	}
   }
   if v.Kind() == reflect.String {
   	fmt.Println("是字符串")
   } else if v.Kind() == reflect.Pointer {
   	ov := v.Elem()
   	if ov.Kind() == reflect.String {
   		fmt.Println("是字符串指针")
   	}
   }

}

func main() {
   fmt.Println("==============传入结构体========================")
   var human Human = Human{
   	Name: "meow",
   	Age:  20,
   }
   inspector(human)
   fmt.Println("===================================================")
   fmt.Println()
   fmt.Println("===============传入结构体指针=======================")
   inspector(&human)
   fmt.Println("===================================================")
   fmt.Println()
   fmt.Println("=================传入字符串=====================")
   var a string = "hello world"
   inspector(a)
   fmt.Println("======================================")
   fmt.Println()
   fmt.Println("================传入字符串指针======================")
   inspector(&a)
   fmt.Println("======================================")
}

==============传入结构体========================
是结构体
===================================================

===============传入结构体指针=======================
是结构体指针
===================================================

=================传入字符串=====================
是字符串
======================================

================传入字符串指针======================
是字符串指针
======================================

通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中使用专有的Elem()方法来获取指针对应的值。

修改字符串:


package main

import (
	"fmt"
	"reflect"
)

type Human struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func inspector(data any) {
	v := reflect.ValueOf(data)
	if v.Elem().Kind() == reflect.String {
		v.Elem().SetString("meowrain")
	}
}
func main() {
	str := "helloworld"
	inspector(&str)
	fmt.Println(str)
}

修改结构体