什么是反射
反射是指在程序运行的时候,可以修改它本身状态的一种行为和能力,它能获取到一些元数据,比如java语言中类的成员变量,方法,注解等元数据,go语言中结构体的field,方法等等,有了反射可以让语言变得更加灵活和更多的功能,比如java中的jdk自带的动态代理就用到了反射,再比如很多框架和封装的ORM也大量用到了反射机制,我们来了解一下go语言的反射。
GO语言中的反射
go是一个比较年轻的语言,吸收了很多语言的优点,同时也弥补了他们的不足。go是一个静态的语言,也带有反射机制,反射要获取的元数据,那整理下go语言的结构
- 结构体(struct,interface)
- 结构体里面的属性(field)
- 方法(method)
基于以上go语言提供的元数据,我们来学习go语言提供的反射机制
go语言将反射的元数据分为两个结构体来描述
类型(TypeOf)
可以获取要目标结构体/属性/方法所属对象的类型
新建结构体:
type Cat struct {
Sex string
Color string
Age int
}
//为结构体提供两个方法
//吃的方法,没有入参
func (cat Cat) Eat() int {
fmt.Println("fhaskfd")
return 2
}
//唱歌的方法,具有两个入参
func (cat Cat) Sing(sing string,i int) int {
fmt.Println(sing)
fmt.Println(i,"66666")
return 2
}
//测试TypeOf提供的方法:
func test1(){
//新建对象
cat := Cat{Sex:"male",Color:"red",Age:18}
typeOf := reflect.TypeOf(cat)
fmt.Println(typeOf)//输出:main.Cat -->包名.结构体名称
}
Kind()
go语言给go中的类型提供了一下枚举:
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
Ptr
Slice
String
Struct
UnsafePointer
)
kind()返回目标type的具体类型
typeOf := reflect.TypeOf(cat)
kind := typeOf.Kind()
//输入Cat反射之后是一个结构体类型
fmt.Println(kind)//输出:struct
//新建一个string类型的来获取Kind
s := "Hello"
fmt.Println(reflect.TypeOf(s).Kind())//输出:string
Name()
Name在其包中返回已定义类型的类型名称
//类型的名称
name := typeOf.Name()
fmt.Println(name)//输出:Cat
fmt.Println(reflect.TypeOf(s).Name())//输出:string
PkgPath()
//导入的包的路径
pkgPath := typeOf.PkgPath()
fmt.Println(pkgPath)//输出main
fmt.Println(reflect.TypeOf(s).PkgPath())//输出“”空字符串
NumField()
返回结构体的属性的数量,如果不是结构体类型则panic
numField := typeOf.NumField()
fmt.Println(numField)//输出:3
Field(i int)
返回结构体中第i个属性的StructField
StructField是这样定义的:
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type
Tag StructTag // field tag string
Offset uintptr // offset within struct, in bytes
Index []int // index sequence for Type.FieldByIndex
Anonymous bool // is an embedded field
}
我们用一个例子来看看:
for i := 0; i < typeOf.NumField(); i++ {
fmt.Println(typeOf.Field(i))
}
//输出:
{Sex string 0 [0] false}
{Color string 16 [1] false}
{Age int 32 [2] false}
//在Name和Type中间的PkgPath()其实是有值的,不过是“”空字符串,
//由于tag没有,所以这里也是空字符串
FieldByIndex(index []int) StructField
FieldByIndex返回对应的嵌套字段,链式调用,如果非结构体则panic
//新建结构体,里面只有一个匿名属性Cat
Animal{
Cat
}
i := []int{0,1}//访问结构体Animal里面的index为0的属性-->Cat---->访问Cat里面的第一个属性---Color
fmt.Println(reflect.TypeOf(Animal{Cat{"mel", "red", 222,}}).FieldByIndex(i))
//输出: {Color string 16 [1] false}
FieldByName(name string) (StructField, bool)
根据属性名称来获取Field,如果找到该属性,返回structField,并返回true,如果找不到返回nil,false
field, b := typeOf.FieldByName("Sex")
fmt.Println(field, b)//输出:{Sex string 0 [0] false} true
field, b := typeOf.FieldByName("Sex")
fmt.Println(field, b)//输出:{ <nil> 0 [] false} false
NumMethod() int
结构体所具有的方法个数
//获取结构体所具有的方法
numMethod := typeOf.NumMethod()
fmt.Println(numMethod)//输出:2
Method(int) Method
// Method represents a single method.
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
返回结构体方法的第i个方法的结构
method := typeOf.Method(0)
fmt.Println(method)//输出:{Eat func(main.Cat) int <func(main.Cat) int Value> 0}
值(TypeValue)
可以获取目标对象的值
新建结构体Cat
type Cat struct {
Sex string
Color string
Age int
}
获取Value:
valueOf := reflect.ValueOf(Cat{"mel", "red", 222,})
fmt.Println(valueOf)//直接输出结构体的值:{man blue 3}
Kind() Kind
返回Value的Kind类型
kind := valueOf.Kind()
fmt.Println(kind)
大集合
由于方法比较多,和TypeOf有些相似,大致说一说他们的用途
//获取结构体方法的个数
fmt.Println(valueOf.NumMethod())
//获取结构体属性的个数
fmt.Println(valueOf.NumField())
//根据索引获取结构体方法的值
fmt.Println(valueOf.Method(0))
//根据索引获取结构体的属性的值
fmt.Println(valueOf.Field(0).Interface())
用反射修改属性的值
func main(){
test3(&Cat{"man", "black", 11,})
}
func test3(o interface{}) {
valueOf := reflect.ValueOf(o)
fmt.Println(valueOf.Kind())
if valueOf.Kind() == reflect.Ptr && !valueOf.Elem().CanSet() {
fmt.Println("修改失败")
return
}
//获取原始对象的引用并创建新对象
elem := valueOf.Elem()
name := elem.FieldByName("Sex")
if !name.IsValid() {
fmt.Println("修改失败")
return
}
//设置值
if name.Kind() == reflect.String {
name.SetString("women")
}
fmt.Println(valueOf)
}
用反射调用方法并传参接收值
func main(){
ReflectMethod(&Cat{"man", "black", 11,})
}
func ReflectMethod(o interface{}) {
v := reflect.ValueOf(o)
//无参函数调用
m1 := v.MethodByName("Eat")
//调用无参方法
call := m1.Call([]reflect.Value{})
for key, value := range call {
fmt.Println(key, value)
}
//有参函数调用
m2 := v.MethodByName("Sing")
values := m2.Call([]reflect.Value{reflect.ValueOf("iris"), reflect.ValueOf(3)})
for _, value := range values {
fmt.Println(value)
}
}
总结:
go语言为我们提供了遍历的反射机制,让我们能在程序运行的过程中修改元数据的值(可以修改的),利用反射并结合设计模式能设计出良好的程序,化繁为简。加油加油啊