反射概述
- 通常的程序逻辑是:编码时先写好剧本,运行时按照写好的剧本演
- 何时创建一个什么实例,给哪个属性赋什么值,然后调用它的哪个方法都毫厘不差
- 但能否在运行时动态地生成“剧本”呢?
- 根据具体的业务需要见机行事,动态地生成一个不知道具体类型会是什么的实例,动态地访问一个无法提前预知的属性或方法
- 答案是可以的,这便引出了今天的主角——反射
应用场景举例:导出商品列表到Excel
- 需求是:不管用户在界面上看到什么商品列表,当它捅一下导出按钮,就将该商品的所有属性和值写出为文件;
- 本例的难点是:我们无法预知用户会选择导出什么类型的商品数据、它有哪些属性,也就无法相应地去创建Excel数据表的列(读写Excel有相应的库,你也可以选择写出为文本文件、写出到数据库表中等等)
- 因为商品的种类太多,如果用正射去做,那么有多少商品类型我们就要写多少个switch或if分支,然后在每一个分支里根据当前分支的具体商品类型去构造相应的Excel数据列,这显然是蹩脚并且很难维护和扩展的
- 而通过反射来做就易如反掌了,管你来的是什么商品实例,立即动态解析得到其类别、所有属性名值,然后根据属性名去创建Excel的列,根据属性值去做数据的填充和写入
定义结构体
type Human struct {
Name string "姓名"
Age int "年龄"
}
func (h *Human) GetName() string {
fmt.Println("GetName called",h.Name)
return h.Name
}
func (h *Human) SetName(name string) {
fmt.Println("SetName called:", name)
h.Name = name
}
func (h *Human) Eat(food string, grams int) (power int) {
fmt.Println("Eat called:", food, grams)
return 5 * grams
}
反射功能一览
func main() {
//创建对象(在实际开发中可以是任意未知类型)
h := Human{"bill", 60}
//获取任意对象的类型和值
getObjTypeValue(h)
//获得任意对象的所有属性
getObjFields(h)
//获取任意对象的所有方法
getObjMethods(&h)
//修改对象任意属性的值
modifyFieldValue(&h)
//动态调用对象的任意方法
callMethods(&h)
fmt.Println("after:h=", h)
}
获取任意对象的类型和值
func getObjTypeValue(obj interface{}) {
oType := reflect.TypeOf(obj)
oKind := oType.Kind()
fmt.Println(oType, oKind)
oValue := reflect.ValueOf(obj)
fmt.Println(oValue)
}
获得任意对象的所有属性
func getObjFields(obj interface{}) {
oType := reflect.TypeOf(obj)
oValue := reflect.ValueOf(obj)
fieldsCount := oType.NumField()
for i := 0; i < fieldsCount; i++ {
//从对象值中获取第i个属性的值,进而值的“正射”形式
fValue := oValue.Field(i).Interface()
structField := oType.Field(i)
fmt.Println(structField.Name, structField.Type, structField.Tag, fValue)
}
}
获取任意对象的所有方法
func getObjMethods(obj interface{}) {
oType := reflect.TypeOf(obj)
fmt.Println(oType.NumMethod())
for i := 0; i < oType.NumMethod(); i++ {
method := oType.Method(i)
fmt.Println(method.Name, method.Type)
}
}
修改对象任意属性的值
func modifyFieldValue(objPtr interface{}) {
//得到【指针的Value】
oPtrValue := reflect.ValueOf(objPtr)
//得到【指针所指向的值(结构体)的Value】
oValue := oPtrValue.Elem()
fmt.Println(oValue)
//遍历所有属性的值
for i := 0; i < oValue.NumField(); i++ {
//根据序号拿到【属性的Value】
fValue := oValue.Field(i)
//拿到属性值的原生类型
fKind := fValue.Kind()
//fmt.Println(fKind)
//根据不同的原生类型设置为不同的值
switch fKind {
case reflect.String:
fValue.SetString("张全蛋")
case reflect.Int:
fValue.SetInt(99)
case reflect.Bool:
fValue.SetBool(false)
default:
fmt.Println("设置为其他值...")
}
}
}
动态调用对象的任意方法
func callMethods(objPtr interface{}) {
//要通过对象的oType拿取方法的参数列表(oType.In(i))
oType := reflect.TypeOf(objPtr)
//要通过对象的oValue拿取方法的具体实现(methodValue)
oValue := reflect.ValueOf(objPtr)
//根据方法的数量进行遍历
for i := 0; i < oType.NumMethod(); i++ {
//预定义要传值的参数切片[]Value
args := make([]reflect.Value, 0)
//从对象的oType拿到当前的方法的methodType
methodType := oType.Method(i).Type
//根据参数个数进行遍历
//为每个参数乱怼一个同种类型反射值,丢入参数列表
//内层循环走完时,当前方法的参数列表就形成了
for j:=0;j<methodType.NumIn();j++{
//从方法的methodType获取当前参数artType
artType := methodType.In(j)
//再获取参数类型artType的原生类型
argKind := artType.Kind()
//根据不同的参数原生类型乱怼相同类型的值
switch argKind {
case reflect.String:
//获取字符串"一坨翔"的反射值Value,丢入参数列表
args = append(args,reflect.ValueOf("一坨翔"))
case reflect.Int:
args = append(args,reflect.ValueOf(100))
case reflect.Bool:
args = append(args,reflect.ValueOf(false))
}
}
//从对象值oValue中获取当前方法值methodValue
methodValue := oValue.Method(i)
//使用参数列表+方法值,反射调用当前方法
methodValue.Call(args)
}
}