文章目录
反射
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("=============== 示例结束 ===============")
}