反射是什么
反射允许程序在运行时访问和修改程序运行时信息,比如:获取变量的类型信息、值、方法,修改变量的值,调用方法等等,反射的功能很强大,但反射是把双刃剑,代码可读性极差。
Go反射没有Java反射功能完备,Java反射还能创建对象。
Go语言反射
Go语言的反射API都定义在reflect
包中,Go语言反射定义了两个核心类型:
- Type
- Value
通过Type和Value两个类型,就能访问变量的类型信息和值
实操
先定义好类型
type PFloat64 float64
type Cat struct {
Color string
}
type Learn interface {
Study()
DoHomework(hour int)
}
// Class 表示班级
type Class struct {
Num int // 班级号
Amount int // 班级人数
}
func (c Class) getPersonAmount() int {
return c.Amount
}
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
Grade string
height int
Class
}
func (s Student) Study() {
fmt.Println(s.Name, "Study!")
}
func (s Student) Sleep() {
fmt.Println(s.Name, "Sleep!")
}
func (s Student) GetAddr() string {
return s.Addr
}
func (s Student) GetGrade() string {
return s.Grade
}
func (s Student) DoHomework(hour int) {
fmt.Printf("%s DoHomework %d hour\n", s.Name, hour)
}
type Student2 struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
Grade string
height int
Class
}
func (s *Student2) Study() {
fmt.Println(s.Name, "Study!")
}
func (s *Student2) Sleep() {
fmt.Println(s.Name, "Sleep!")
}
func (s *Student2) GetAddr() string {
return s.Addr
}
func (s *Student2) GetGrade() string {
return s.Grade
}
func (s *Student2) DoHomework(hour int) {
fmt.Printf("%s DoHomework %d hour\n", s.Name, hour)
}
获取类型信息
使用TypeOf
方法获取类型信息。注意Type
和Kind
的区别,Type是指具体类型,Kind是指底层类型。
// 测试各种类型
func Test1(t *testing.T) {
var f0 float64 = 3.14
typeOfF0 := reflect.TypeOf(f0)
fmt.Println("Name:", typeOfF0.Name())
fmt.Println("Kind:", typeOfF0.Kind())
var f PFloat64 = 3.15
t1 := reflect.TypeOf(f)
fmt.Println("Name:", t1.Name())
fmt.Println("Kind:", t1.Kind())
cat := Cat{
Color: "橘色",
}
typeOfCat := reflect.TypeOf(cat)
fmt.Println("Name:", typeOfCat.Name())
fmt.Println("Kind:", typeOfCat.Kind())
}
Elem
方法获取指针类型指向的变量
func Test2(t *testing.T) {
cat := Cat{
Color: "橘色",
}
typeOfCatPtr := reflect.TypeOf(&cat)
fmt.Println("Name:", typeOfCatPtr.Name(), "len:", len(typeOfCatPtr.Name()))
fmt.Println("Kind:", typeOfCatPtr.Kind())
elem := typeOfCatPtr.Elem() // 取指针指向的元素,相当于用*号解引用
fmt.Println("Name:", elem.Name())
fmt.Println("Kind:", elem.Kind())
fmt.Println("Elem:", elem.Elem()) // panic: reflect: Elem of invalid type main.Cat
}
值
使用ValueOf
方法获取值;使用SetXXX
方法修改值。
// 测试通过反射修改变量值
func Test5(t *testing.T) {
var x float64 = 3.14
valueOfX := reflect.ValueOf(x)
// 获取值
fmt.Println("valueOfX.value:", valueOfX.Interface().(float64))
fmt.Println("CanSet:", valueOfX.CanSet())
//valueOfX.SetFloat(6.66) // panic: reflect: reflect.Value.SetFloat using unaddressable value。因为valueOfX是x变量副本的值,并不能改变原本的x变量值,类似函数想修改函数外的变量值时必须传变量的指针
valueOfXPtr := reflect.ValueOf(&x)
fmt.Println("valueOfXPtr.value:", valueOfXPtr.Interface())
fmt.Println("CanSet:", valueOfXPtr.CanSet())
//valueOfXPtr.SetFloat(7.77) // panic: reflect: reflect.Value.SetFloat using unaddressable value
elem := valueOfXPtr.Elem()
fmt.Println("CanSet:", elem.CanSet())
elem.SetFloat(8.88)
fmt.Println("x:", x)
fmt.Println("elem.Value:", elem.Interface().(float64))
elem.Set(reflect.ValueOf(9.99))
fmt.Println("x:", x)
fmt.Println("elem.Value:", elem.Interface().(float64))
}
访问结构体字段
获取结构体字段信息
// 测试获取结构体的字段
func Test3(t *testing.T) {
stu := Student{}
typeOfStu := reflect.TypeOf(stu)
fmt.Println("NumField:", typeOfStu.NumField())
for i := 0; i < typeOfStu.NumField(); i++ {
field := typeOfStu.Field(i)
fmt.Println("Name:", field.Name, ",Index:", field.Index, ",Tag:", field.Tag, ",Offset:", field.Offset, ",PkgPath:", field.PkgPath, ",IsExported:", field.IsExported(), ",Anonymous:", field.Anonymous)
}
heightField, ok := typeOfStu.FieldByName("height")
fmt.Println("heightField.ok:", ok, "heightField.Type:", heightField.Type)
weightField, ok := typeOfStu.FieldByName("weight")
fmt.Println("weightField.ok:", ok, "weightField.Type:", weightField.Type)
field5 := typeOfStu.FieldByIndex([]int{5})
fmt.Println("Name:", field5.Name)
field5_0 := typeOfStu.FieldByIndex([]int{5, 0}) // 可以多层访问结构体字段
fmt.Println("Name:", field5_0.Name, ",Index:", field5_0.Index)
field5_1 := typeOfStu.FieldByIndex([]int{5, 1})
fmt.Println("Name:", field5_1.Name, ",Index:", field5_1.Index)
//typeOfStu.FieldByIndex([]int{6}) // panic: reflect: Field index out of bounds。越界
nameField, ok := typeOfStu.FieldByName("Name")
if !ok {
t.Fatal("fatal: not exist `name` field")
}
// 结构体标签
structTag := nameField.Tag
jsonTag := structTag.Get("json") // 结构体标签的写法必须严格遵守规则:键和值用冒号分隔,值用双引号括起来,多个键值对之间用空格分隔
json2Tag := structTag.Get("json2")
fmt.Println("jsonTag:", jsonTag, ",json2Tag:", json2Tag)
matchedField, ok := typeOfStu.FieldByNameFunc(func(s string) bool {
//if s == "Name" || s == "Age" { // 这样不行,必须唯一匹配一个,否则返回没匹配到字段
if s == "Name" {
return true
}
return false
})
fmt.Println("matchedField.ok:", ok, ",matchedField.Name:", matchedField.Name, ",matchedField.Index:", matchedField.Index)
}
修改结构体字段值
// 测试通过反射修改结构体字段
func Test6(t *testing.T) {
stu := Student{
Name: "PENG",
Age: 16,
}
var learn Learn = stu
typeOfLearn := reflect.TypeOf(learn) // 返回的是动态类型
//typeOfLearn.Elem()
fmt.Println("Name:", typeOfLearn.Name(), ",Kind:", typeOfLearn.Kind())
elem := reflect.ValueOf(&stu).Elem()
for i := 0; i < elem.NumField(); i++ {
field := elem.Type().Field(i)
fieldValue := elem.Field(i)
fmt.Println("======================")
fmt.Println("field.Type == fieldValue.Type():", field.Type == fieldValue.Type(), ",field.Type.Name():", field.Type.Name())
fmt.Println("field.Name:", field.Name, "field.Index:", field.Index, ",field.Type.Kind:", field.Type.Kind(), ",field.IsExported:", field.IsExported())
if field.IsExported() {
fmt.Println("fieldValue.Interface:", fieldValue.Interface())
}
}
fmt.Println("-----修改结构体字段值-----")
nameField, ok := elem.Type().FieldByName("Name")
fmt.Println("nameField.Index:", nameField.Index, ",nameField.Type:", nameField.Type, ",ok:", ok)
nameFieldValue := elem.FieldByName("Name")
fmt.Println("nameFieldValue.Interface():", nameFieldValue.Interface())
nameFieldValue.SetString("XZP")
fmt.Println("stu.Name:", stu.Name)
}
访问类型定义的方法
获取方法信息
// 测试获取类型定义的方法
// 另外,测测包装方法,发现如下现象:方法接收器是Student类型时,*Student类型也有对应的4个方法(证明了包装方法的存在);方法接收器是*Student2类型时,Student2类型却没有对应方法。
func Test4(t *testing.T) {
stu1 := Student{
Name: "李六",
Age: 21,
Addr: "ddd",
}
typeOfStu1 := reflect.TypeOf(stu1)
fmt.Println("typeOfStu1.Name:", typeOfStu1.Name(), ",typeOfStu1.Kind:", typeOfStu1.Kind(), ",typeOfStu1.NumMethod:", typeOfStu1.NumMethod()) // typeOfStu1.Name: Student ,typeOfStu1.Kind: struct ,typeOfStu1.NumMethod: 4
for i := 0; i < typeOfStu1.NumMethod(); i++ {
method := typeOfStu1.Method(i)
fmt.Printf("typeOfStu1 method[%d].Name:%s\n", method.Index, method.Name)
}
fmt.Println("------------------------------------")
stu1Ptr := &Student{
Name: "李六指针",
Age: 210,
Addr: "ddd ptr",
}
typeOfStu1Ptr := reflect.TypeOf(stu1Ptr)
fmt.Println("typeOfStu1Ptr.Name:", typeOfStu1Ptr.Name(), ",typeOfStu1Ptr.Kind:", typeOfStu1Ptr.Kind(), ",typeOfStu1Ptr.NumMethod:", typeOfStu1Ptr.NumMethod()) // typeOfStu1Ptr.Name: ,typeOfStu1Ptr.Kind: ptr ,typeOfStu1Ptr.NumMethod: 4
for i := 0; i < typeOfStu1Ptr.NumMethod(); i++ {
method := typeOfStu1Ptr.Method(i)
fmt.Printf("typeOfStu1Ptr method[%d].Name:%s\n", method.Index, method.Name)
}
fmt.Println("------------------------------------")
stu2 := Student2{
Name: "赵七",
Age: 22,
Addr: "eee",
}
typeOfStu2 := reflect.TypeOf(stu2)
fmt.Println("typeOfStu2.Name:", typeOfStu2.Name(), ",typeOfStu2.Kind:", typeOfStu2.Kind(), ",typeOfStu2.NumMethod:", typeOfStu2.NumMethod()) // typeOfStu2.Name: Student2 ,typeOfStu2.Kind: struct ,typeOfStu2.NumMethod: 0
fmt.Println("------------------------------------")
stu2Ptr := &Student2{
Name: "赵七指针",
Age: 220,
Addr: "eee ptr",
}
typeOfStu2Ptr := reflect.TypeOf(stu2Ptr)
fmt.Println("typeOfStu2Ptr.Name:", typeOfStu2Ptr.Name(), ",typeOfStu2Ptr.Kind:", typeOfStu2Ptr.Kind(), ",typeOfStu2Ptr.NumMethod:", typeOfStu2Ptr.NumMethod()) // typeOfStu2Ptr.Name: ,typeOfStu2Ptr.Kind: ptr ,typeOfStu2Ptr.NumMethod: 4
for i := 0; i < typeOfStu2Ptr.NumMethod(); i++ {
method := typeOfStu2Ptr.Method(i)
fmt.Printf("typeOfStu2Ptr method[%d].Name:%s\n", method.Index, method.Name)
}
}
调用方法
// 测试反射调用方法
func Test44(t *testing.T) {
stu := Student{
Name: "张三",
Age: 18,
Addr: "aaa",
Class: Class{
Num: 0,
Amount: 30,
},
}
valueOfStu := reflect.ValueOf(stu)
// 无参无返回值
methodSleep := valueOfStu.MethodByName("Sleep")
result := methodSleep.Call([]reflect.Value{})
fmt.Println("len(methodSleep.result):", len(result))
// 无参有返回值
methodGetAddr := valueOfStu.MethodByName("GetAddr")
result = methodGetAddr.Call([]reflect.Value{})
fmt.Printf("len(methodGetAddr.result):%d, result:%s\n", len(result), result[0].String())
// 有参无返回值
methodDoHomework := valueOfStu.MethodByName("DoHomework")
result = methodDoHomework.Call([]reflect.Value{reflect.ValueOf(2)})
fmt.Printf("len(methodDoHomework.result):%d\n", len(result))
}
判断一个类型是否实现了某接口
使用Implements
方法,测试代码如下:
type MyErr string
// 实现error接口
func (m *MyErr) Error() string {
return string(*m)
}
func Test1(t *testing.T) {
myErr := MyErr("自定义err")
typeOfMyErr := reflect.TypeOf(myErr)
fmt.Printf("typeOfMyErr.Name:%s, typeOfMyErr.Kind:%v\n", typeOfMyErr.Name(), typeOfMyErr.Kind())
typeOfErrorPtr := reflect.TypeOf((*error)(nil))
fmt.Printf("typeOfErrorPtr.Name:%s, typeOfErrorPtr.Kind:%v\n", typeOfErrorPtr.Name(), typeOfErrorPtr.Kind())
// 获取error接口的反射类型
typeOfErrorPtrElem := typeOfErrorPtr.Elem()
fmt.Printf("typeOfErrorPtrElem.Name:%s, typeOfErrorPtrElem.Kind:%v\n", typeOfErrorPtrElem.Name(), typeOfErrorPtrElem.Kind())
ok := typeOfMyErr.Implements(typeOfErrorPtrElem)
fmt.Println("ok?", ok)
ok = reflect.TypeOf(&myErr).Implements(typeOfErrorPtrElem)
fmt.Println("ok?", ok)
}
执行结果:
=== RUN Test1
typeOfMyErr.Name:MyErr, typeOfMyErr.Kind:string
typeOfErrorPtr.Name:, typeOfErrorPtr.Kind:ptr
typeOfErrorPtrElem.Name:error, typeOfErrorPtrElem.Kind:interface
ok? false
ok? true
--- PASS: Test1 (0.00s)
PASS
重点:
在go语言中,想要获取接口类型的reflect.Type
对象,只能这样获取:reflect.TypeOf((*SomeInterface)(nil)).Elem()
。