Go中的反射

文章目录

反射

java中的反射就是通过运行时对象获取其对应的方法属性等

Go中则是和接口有关,在Go中,接口本质上是结构体

第一种:有方法集:说白了就是内部有一个或多个定义的方法

go 复制代码
type iface struct {
   tab  *itab // 包含 数据类型,接口类型,方法集等
   data unsafe.Pointer // 指向值的指针
}

第二种:没有方法集,这种就是我在go进阶语法中说的空接口

go 复制代码
type eface struct {
   _type *_type // 类型
   data  unsafe.Pointer // 指向值的指针
}

空接口只有"类型 + 数据",非空接口多一个"方法表"。

Go 用方法表来实现动态派发,用 emptyInterface 来实现任意类型存储。

所以这里应该明白了为什么用接口来反射了把,就是因为接口可以接受任意类型的数据和java的Object一样,通过反射工具包,来获取go接口中实际存储类型的属性方法等

类型

type和kind

go 复制代码
func test20() {
	a := int8(111)
	/**
	这里的type是获取到变量对应类型的所有特征集如:
	类型名字(Name)
	包路径(PkgPath)
	大小(Size)
	对齐(Align)
	是否可比较(Comparable)
	方法列表(Methods)
	字段信息(Fields)
	能否转换(ConvertibleTo)
	是否实现某接口(Implements)
	若是指针,指向什么(Elem)
	若是 map,key/value 类型是什么(Key, Elem)
	若是 slice,元素类型是什么(Elem)
	*/
	aType := reflect.TypeOf(a)
	fmt.Println(aType)
	/**
	 Kind 是 reflect.Kind 类型,是一个枚举。用来告诉你这个值属于哪一类:
		  int8, int, float64, struct, slice, map, ptr, interface ...
	 它只表示"分类",不包含其他任何信息。
	*/
	kind := aType.Kind()
	fmt.Println(kind)
}

Elem:获取到指针,切片,数组,通道,映射表的具体类型

获取到指针,切片,数组,通道,映射表的具体类型

go 复制代码
func test21() {
	m := map[string]int{}
	t := reflect.TypeOf(m)

	fmt.Println(t)        // map[string]int
	fmt.Println(t.Kind()) // map

	// 注意只有map类型才有t.Key()方法来获取map的key的类型,其他类型会panic
	fmt.Println(t.Key())  // string
	fmt.Println(t.Elem()) // int

	fmt.Println(reflect.TypeOf(make([]string, 0, 1)).Elem()) // string

	tmp := 1
	intValue := &tmp
	fmt.Println(reflect.TypeOf(intValue).Elem())// int ,因为intValue是一个int类型的指针
}

Size:获取对应类型的所占字节大小

获取对应类型的所占字节大小

go 复制代码
func test22() {
	a := make([]int, 1, 128)
	fmt.Println(reflect.TypeOf(a).Size()) // 24
	/**
	 解释:
	type slice struct {
	    Data uintptr  // 指向底层 array 的指针     (8 字节)
	    Len  int      // 切片当前长度              (8 字节)
	    Cap  int      // 切片容量                  (8 字节)
	}
	所以切片类型所占的字节大小为24
	*/

	b := 1
	fmt.Println(reflect.TypeOf(b).Size())//8
}

Comparable:判断一个类型是否可以被比较

go 复制代码
func test23() {
	fmt.Println(reflect.TypeOf("hello world!").Comparable())// true
	fmt.Println(reflect.TypeOf(1024).Comparable())//true
	fmt.Println(reflect.TypeOf([]int{}).Comparable())// false
	fmt.Println(reflect.TypeOf(struct{}{}).Comparable())//true
}

Implements:判断一个类型是否实现了某个接口

go 复制代码
func main() {
	test24()
}

func test24() {
	studentType := reflect.TypeOf(&Student{})
	sayHello := reflect.TypeOf(new(SayHello)).Elem()
	fmt.Println(studentType.Implements(sayHello)) // true
}

type SayHello interface {
	sayHello() string
}
type Student struct {
	name string
}

func (s *Student) sayHello() string {
	return s.name + "hello"
}

// 注意studentType := reflect.TypeOf(&Student{})如果这里变成了studentType := reflect.TypeOf(Student{}),就会返回false,因为是*Student 类型实现了接口方法不是Student类型

ConvertibleTo:判断一个类型是否可以被转换为另一个指定的类型

go 复制代码
func test25() {
	a := "1"
	aType := reflect.TypeOf(a)
	fmt.Println(aType.ConvertibleTo(reflect.TypeOf(int(0)))) // false
	/**
	想判断 "1" 是否能转为 int → 用 strconv.Atoi
	reflect.ConvertibleTo 只能判断编译期类型转换,不包括字符串解析
	string → int 在 reflect 中永远是 false
	reflect 判断是语言级转换,不是数据解析
	*/
}

值:ValueOf

Type和Elem

Type:

go 复制代码
	a := 1
	aValue := reflect.ValueOf(a)

	// 下面2种写法等价
	aType1 := aValue.Type()
	aType2 := reflect.TypeOf(a)
	fmt.Println(aType1, aType2)

elem:

go 复制代码
func test26() {
	a := 1
	aValue := reflect.ValueOf(a)

	// 下面2种写法等价
	aType1 := aValue.Type()
	aType2 := reflect.TypeOf(a)
	fmt.Println(aType1, aType2)

	aData := aValue.Elem()
	fmt.Println(aData.Interface())
}

上面的写法是会报错的,因为Elem()是用来获取容器中的具体值的,取出"指针、slice、map、array、chan、interface"的元素类型对应的 Value,而上面的不是容器,所有不能使用Elem()

正确的写法:

go 复制代码
func test26() {
	tmp := 1
	a := &tmp
	aValue := reflect.ValueOf(a)

	aData := aValue.Elem()
	fmt.Println(aData.Int()) //转为int64
	fmt.Println(aData.Interface())// 转为接口类型
	fmt.Println(aData.String())// 转为string类型,但是这个例子会返回<int Value>,因为a是int类型
}

获取值的地址

go 复制代码
func test27() {
	a := 1
	fmt.Println(*(reflect.ValueOf(&a).Elem().Addr().Interface().(*int)))
}

获取地址,转为*int类型,解引用

设置值

go 复制代码
func test28() {
	num := new(int)
	*num = 10

	// 获取指针指向的元素
	numValue := reflect.ValueOf(num).Elem()
	numValue.SetInt(111)
	fmt.Println(numValue)
}

获取值

go 复制代码
func test29() {
	a := interface{}("aaa")
	aValue := reflect.ValueOf(a)
	value, ok := aValue.Interface().(string)
	fmt.Println(value, ok)
}

value, ok := aValue.Interface().(string),如果这里填入的不是和a类型一样的类型,这回打印填写类型的0值和false

函数

获取函数的基本信息

go 复制代码
func main() {
	// 输出函数名称,字面量函数的类型没有名称
	a := reflect.TypeOf(sum)
	fmt.Println(a.Name())

	// 输出参数,返回值的数量
	fmt.Println(a.NumIn(), a.NumOut())

	// 输出第一个参数的类型
	rParamType := a.In(0)
	fmt.Println(rParamType.Kind())

	// 输出第一个返回值的类型
	aType := a.Out(0)
	fmt.Println(aType.Kind())
}

func sum(a, b int) int {
	return a + b
}

反射调用函数

go 复制代码
func main() {
	// 获取函数的反射值
	rType := reflect.ValueOf(sum)
	// 传入参数数组
	rResValue := rType.Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(22)})
	for _, value := range rResValue {
		fmt.Println(value.Interface())
	}
}

func sum(a, b int) int {
	return a + b
}

结构体

go 复制代码
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

// Person 结构体,包含:
// - 三个导出字段(首字母大写):可以被其他包和反射正常修改
// - 一个未导出字段 money(首字母小写):正常反射不能直接修改
type Person struct {
	Name    string `json:"name"`    // Tag: json 序列化用的字段名
	Age     int    `json:"age"`     // Tag: json 字段 age
	Address string `json:"address"` // Tag: json 字段 address
	money   int                     // 未导出字段(私有)
}

// Person 的一个值接收者方法
// 这个方法会出现在 reflect.Type / reflect.Value 的方法集中
func (p Person) Talk(msg string) string {
	return msg
}

func main() {

	// ==========================================================
	// 一、获取结构体类型信息(reflect.Type)
	// ==========================================================

	// new(Person):
	//   - 内建函数 new(T),返回 *T,这里是 *Person
	//   - 分配一个 Person 零值并返回指针
	pointerType := reflect.TypeOf(new(Person))
	// reflect.TypeOf(x):
	//   - 返回 x 的动态类型信息(reflect.Type)
	//   - 这里 x 是 *Person,所以返回的 Type 表示 "*main.Person"
	fmt.Println("pointerType:", pointerType)

	// Elem():
	//   - 对于指针类型 *T,Elem() 返回其指向的类型 T
	//   - 这里是从 *Person 拿到 Person
	rType := pointerType.Elem()
	fmt.Println("rType:", rType) // 输出:main.Person

	// NumField():
	//   - 只对 Kind == Struct 的 Type 有意义
	//   - 返回结构体中"字段的数量"(只看声明,不管是否导出)
	fmt.Println("字段数量:", rType.NumField())
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 二、通过索引访问结构体字段(Type.Field)
	// ==========================================================
	for i := 0; i < rType.NumField(); i++ {
		// Field(i):
		//   - 返回第 i 个字段的 StructField 描述信息
		//   - 包含字段名、类型、Tag、偏移、是否导出等
		structField := rType.Field(i)

		// structField.Index:
		//   - 一个 int 切片,表示访问该字段的"索引路径"
		//   - 用于嵌套结构体时定位字段,这里都是单层,所以形如 [0] [1]...
		// structField.Name:
		//   - 字段名
		// structField.Type:
		//   - 字段类型(reflect.Type)
		// structField.Offset:
		//   - 字段在结构体内存布局中的字节偏移
		// structField.IsExported():
		//   - 是否为导出字段(首字母大写)
		fmt.Printf("字段 #%d: Index=%v Name=%s Type=%v Offset=%d Exported=%v\n",
			i,
			structField.Index,
			structField.Name,
			structField.Type,
			structField.Offset,
			structField.IsExported(), // IsExported():方法,用来判断字段是否导出
		)
	}
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 三、通过名称访问字段 + 访问 Tag
	// ==========================================================

	// FieldByName(name string):
	//   - 按字段名查找,返回 StructField 和 bool(是否找到)
	nameField, ok := rType.FieldByName("Name")
	if ok {
		fmt.Println("通过名称获取字段 Name:", nameField.Name, nameField.Type)

		// nameField.Tag 是 StructTag 类型,底层是 string
		// Lookup(key string):
		//   - 在 Tag 字符串中查找形如 key:"value" 的项
		//   - 返回 (value, true) 或 ("", false)
		jsonValue, found := nameField.Tag.Lookup("json")
		fmt.Println("Tag.Lookup(\"json\") 返回 value, ok:", jsonValue, found)

		// Get(key string):
		//   - 同样查找 key:"value"
		//   - 找不到时返回 ""(不告诉你是否找到)
		jsonValue2 := nameField.Tag.Get("json")
		fmt.Println("Tag.Get(\"json\") 返回:", jsonValue2)
	}

	// 再演示一下访问未导出字段 money
	moneyFieldType, ok := rType.FieldByName("money")
	if ok {
		fmt.Println("字段 money 信息:", moneyFieldType.Name, moneyFieldType.Type, "Exported:", moneyFieldType.IsExported())
	}
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 四、创建结构体实例并获取 reflect.Value(用于修改字段)
	// ==========================================================

	// &Person{...}:
	//   - 创建一个 Person 实例,并取其地址,类型为 *Person
	// reflect.ValueOf(&Person{...}):
	//   - 返回一个 reflect.Value,内部持有 *Person 这个值
	rValuePtr := reflect.ValueOf(&Person{
		Name:    "",
		Age:     0,
		Address: "",
		money:   0,
	})

	// Elem():
	//   - 对于一个指针类型的 Value,Elem() 返回其指向的"元素值"
	//   - 这里返回的就是 Person 本体的 reflect.Value
	rValue := rValuePtr.Elem()

	// FieldByName("Name"):
	//   - 在 Value(这里是 struct)上按字段名查找,返回字段对应的 reflect.Value
	nameValue := rValue.FieldByName("Name")
	// IsValid():
	//   - 判断这个 Value 是否有效(例如字段不存在时返回无效值)
	// CanSet():
	//   - 判断这个 Value 是否可被 Set* 方法修改
	if nameValue.IsValid() && nameValue.CanSet() {
		// SetString(s string):
		//   - 对 Kind == String 的 Value 设置内容
		nameValue.SetString("jack")
	}

	fmt.Printf("修改 Name 后结构体: %+v\n", rValue.Interface())
	// Interface():
	//   - 将 reflect.Value 再还原为一个 interface{},里面装着真实的 Person 值
	//   - 这里用 %+v 打印出字段名和字段值
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 五、修改未导出字段 money(高级用法:NewAt + UnsafePointer)
	// ==========================================================

	// FieldByName("money"):
	//   - 获取未导出字段的 Value
	moneyValue := rValue.FieldByName("money")
	if moneyValue.IsValid() {
		// moneyValue.CanSet() 通常会是 false,因为未导出字段不能直接通过反射修改
		fmt.Println("money 字段 CanSet():", moneyValue.CanSet())

		// moneyValue.Addr():
		//   - 返回一个新的 reflect.Value,表示"该字段的地址"(类型为 *int)
		//   - 注意:返回的是反射包装过的指针值,并不是直接的 *int
		addrValue := moneyValue.Addr()

		// addrValue.Pointer():
		//   - 仅对 Kind 为 Ptr/Chan/Map/Slice/UnsafePointer 等适用
		//   - 返回内部指针的 uintptr 表示(裸地址)
		rawPtrUint := addrValue.Pointer()

		// unsafe.Pointer(rawPtrUint):
		//   - 将 uintptr 转为 unsafe.Pointer(通用裸指针)
		rawPtr := unsafe.Pointer(rawPtrUint)

		// reflect.NewAt(t Type, p unsafe.Pointer):
		//   - 告诉反射系统:在内存地址 p 上,有一个类型为 t 的值
		//   - 返回一个 *T 对应的 reflect.Value(这里相当于 *int 的 Value)
		//   - 这一步绕过了未导出字段的可见性限制,直接从内存层面操作
		ptrValue := reflect.NewAt(moneyValue.Type(), rawPtr)

		// ptrValue.Elem():
		//   - 对于指针类型 Value,返回其指向的"元素 Value"
		//   - 这里得到的就是 money 字段本身的 Value
		fieldValue := ptrValue.Elem()

		// SetInt(i int64):
		//   - 对 Kind == Int 系列的 Value 设置一个 int64 值
		fieldValue.SetInt(164)
	}

	fmt.Printf("修改 money 后结构体: %+v\n", rValue.Interface())
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 六、访问方法信息(Type.NumMethod / Type.Method)
	// ==========================================================

	// NumMethod():
	//   - 返回该类型导出方法的数量(只统计导出方法)
	//   - 对于 Person,只有 Talk 一个方法
	fmt.Println("方法数量:", rType.NumMethod())

	for i := 0; i < rType.NumMethod(); i++ {
		// Method(i):
		//   - 返回第 i 个方法的 Method 结构体
		//   - 包含 Name、PkgPath、Type、Func、Index 等信息
		method := rType.Method(i)

		// method.Type:
		//   - 方法类型,形式类似:func(main.Person, string) string
		//   - 注意:第一个参数是接收者类型(Person)
		fmt.Printf("方法 #%d: Name=%s Type=%v Exported=%v\n",
			i,
			method.Name,
			method.Type,
			method.IsExported(), // IsExported():方法是否导出(首字母大写)
		)

		fmt.Println("  方法参数列表:")
		// method.Func.Type():
		//   - method.Func 是一个 reflect.Value,表示底层的函数对象
		//   - .Type() 返回这个函数的类型(reflect.Type),和 method.Type 一致
		fnType := method.Func.Type()

		// fnType.NumIn():
		//   - 返回函数的输入参数个数(包括接收者)
		for inIndex := 0; inIndex < fnType.NumIn(); inIndex++ {
			// fnType.In(i):
			//   - 返回第 i 个入参的类型
			fmt.Println("   -", fnType.In(inIndex).String())
		}

		fmt.Println("  方法返回值列表:")
		// fnType.NumOut():
		//   - 返回函数输出参数(返回值)的个数
		for outIndex := 0; outIndex < fnType.NumOut(); outIndex++ {
			// fnType.Out(i):
			//   - 返回第 i 个返回值的类型
			fmt.Println("   -", fnType.Out(outIndex).String())
		}
	}
	fmt.Println("-----------------------------------------------------")

	// ==========================================================
	// 七、通过 reflect.Value 调用方法(不需要手动传接收者)
	// ==========================================================

	// reflect.ValueOf(&Person{}).Elem():
	//   - 再创建一个新的 Person 实例的 Value 用于演示调用方法
	personValue := reflect.ValueOf(&Person{Name: "Tom"}).Elem()

	// NumMethod()(Value 版):
	//   - 返回该 Value 所表示的值的方法数量(只包含导出方法)
	fmt.Println("Value 上的方法数量:", personValue.NumMethod())

	// MethodByName("Talk"):
	//   - 从 Value 上按方法名查找方法,返回一个可调用的 reflect.Value
	//   - 这个 Value 的 Kind 为 Func,可以使用 Call 调用
	talkMethodValue := personValue.MethodByName("Talk")
	if talkMethodValue.IsValid() {
		// Call(args []Value):
		//   - 调用该方法
		//   - 参数是一个 []reflect.Value,表示传入的实参列表
		//   - 对于方法调用:接收者不需要自己传,反射系统自动补充
		//   - 这里只需要传入方法定义中的"除接收者以外的参数"
		results := talkMethodValue.Call([]reflect.Value{
			reflect.ValueOf("hello, reflect!"),
		})

		fmt.Println("Talk 方法返回:")
		// results 是 []reflect.Value,包含所有返回值
		for _, rv := range results {
			// rv.Interface():
			//   - 将每个返回值从反射世界还原到正常 Go 值
			fmt.Println("   -", rv.Interface())
		}
	}

	fmt.Println("=============== 示例结束 ===============")
}
相关推荐
T.O.P_KING2 小时前
Common Go Mistakes(IV 字符串)
开发语言·后端·golang
Hello.Reader3 小时前
用纯 Go 实现一个 AES-128 加密 m3u8 视频下载器(不依赖 ffmpeg)
golang·ffmpeg·音视频·m3u8
Zfox_4 小时前
【Go】异常处理、泛型和文件操作
开发语言·后端·golang
zhangyanfei014 小时前
谈谈 Golang 中的线程协程是如何管理栈内存的
开发语言·后端·golang
q***04634 小时前
[golang][MAC]Go环境搭建+VsCode配置
vscode·macos·golang
小信啊啊9 小时前
Go语言结构体
golang·go
古城小栈9 小时前
Go中 巧妙解决 同主版本多子版本共存
后端·golang
芷栀夏9 小时前
多设备文件接力太麻烦?Go File + cpolar让传输效率翻倍
开发语言·后端·golang
风生u18 小时前
go进阶语法
开发语言·后端·golang