Golang入门笔记-CH09-反射
发布时间 : 2022-10-01 00:00
字数:1.6k
阅读 :
反射是用程序检查其所拥有的结构,尤其是类型的一种能力。反射可以在运行时(不必在编译时)检查类型和变量,例如大小、变量、方法和动态调用这些方法。
方法和类型的反射 reflect
包提供了反射功能,它定义两个重要类型:Type
和 Value
,分别表示动态类型和值。
有两个常用的方法:
reflect.TypeOf
:返回对象的具体类型。
reflect.ValueOf
:返回对象的值。
反射是先检查一个接口的值,再将变量转换成空接口类型,我们看下这两个函数的定义就能明白了:
1 2 func TypeOf (i interface {}) Typefunc ValueOf (i interface {}) Value
reflect.TypeOf 函数 reflect.Typeof()
可以接收任意 interface{}
类型数据,并返回其动态类型。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "reflect" ) func main () { t := reflect.TypeOf(3 ) fmt.Println(t.String()) fmt.Println(t) }
由于 reflect.TypeOf
返回的是一个动态类型的接口值,因此它返回的总是具体类型。下面的代码打印将是 *os.File
,而不是 io.Writer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "io" "os" "reflect" ) func main () { var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w)) }
可以通过 reflect.Type
的 Name()
方法获取类型名称 ,通过 reflect.Type
的 Kind()
方法获取底层类型 ,我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) type Enum int func main () { var x Enum = 2 v := reflect.TypeOf(x) fmt.Println(v.Name()) fmt.Println(v.Kind()) }
reflect.ValueOf 函数 reflect.ValueOf()
可以接收任意 interface{}
类型数据,并返回其值。
我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) type Enum int func main () { var x Enum = 2 v := reflect.ValueOf(x) fmt.Printf("%v\n" , v) fmt.Printf("%v" , v.Interface().(Enum)) }
通过反射修改值 反射并不能修改所有变量的值,我们来看一个例子:
1 2 3 4 5 6 7 8 9 10 package mainimport "reflect" func main () { var x int = 2 v := reflect.ValueOf(x) v.SetInt(5 ) }
当运行上述代码时,出现了如下的错误:
出现这个错误的原因是:v 是不可设置 的。我们通过 v := reflect.ValueOf(x)
传递的仅仅是变量 x
的副本,并不能更改原始的 x
。
我们可以利用 CanSet()
函数判断变量是否可设置:
若 CanSet()
返回 false,表明变量无法设置;true
表可设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "reflect" ) func main () { var x int = 2 v := reflect.ValueOf(x) b := v.CanSet() fmt.Println(b) }
要让 v
可设置,我们可以使用 Elem()
方法,相当于间接使用指针:
v := reflect.ValueOf(x)
只是传递了 x
的拷贝,修改 v
并无法修改原始的 x
;若要使修改 v
也能作用到 x
上,需要传递 x
的引用:v := reflect.ValueOf(&x)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "reflect" ) func main () { var x = 2 v := reflect.ValueOf(x) fmt.Printf("setAbility of v: %v\n" , v.CanSet()) v = reflect.ValueOf(&x) fmt.Printf("setAbility of v: %v\n" , v.CanSet()) v = v.Elem() fmt.Printf("setAbility of v: %v\n" , v.CanSet()) v.SetInt(3 ) fmt.Println(v) }
上述代码运行结果为:
反射获取结构体信息 reflect.Type
的 Field()
方法返回 StructField
结构,这个结构用来描述结构体成员的信息:
1 2 3 4 5 6 7 8 9 type StructField struct { Name string PkgPath string Type Type Tag StructTag Offset uintptr Index []int Anonymous bool }
reflect.Type
中的常用方法如下:
方法
说明
Field(i int) StructField
根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机
NumField() int
返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机
FieldByName(name string) (StructField, bool)
根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机
FieldByIndex(index []int) StructField
多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机
FieldByNameFunc( match func(string) bool) (StructField,bool)
根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机
我们可以通过实例化一个结构体,然后再利用 reflect.Type
的 FieldByName()
方法查找结构体中指定字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "reflect" ) type Cat struct { Name string Type int `json:"type" id:"66"` } func main () { cat := Cat{Name: "Kim" , Type: 1 } typeOfCat := reflect.TypeOf(cat) for i := 0 ; i < typeOfCat.NumField(); i++ { fieldType := typeOfCat.Field(i) fmt.Printf("name: %v tag: %v\n" , fieldType.Name, fieldType.Tag) } if catType, ok := typeOfCat.FieldByName("Type" ); ok { fmt.Println(catType.Tag.Get("json" ), catType.Tag.Get("id" )) } }
上述代码运行结果如下:
我们也可以通过反射来修改结构体的成员变量,但前提是这些成员变量必须是可导出的(首字母大写),来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "reflect" ) type Cat struct { name string Type int `json:"type" id:"66"` } func main () { cat := Cat{name: "Kim" , Type: 1 } valOfCat := reflect.ValueOf(&cat).Elem() valOfCat.Field(0 ).SetString("Mi" ) }
上述代码运行后报错了:
因为 name
字段名是小写字母开头,无法导出。我们将其改为 Name
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "reflect" ) type Cat struct { Name string Type int `json:"type" id:"66"` } func main () { cat := Cat{Name: "Kim" , Type: 1 } valOfCat := reflect.ValueOf(&cat).Elem() valOfCat.Field(0 ).SetString("Mi" ) fmt.Println(valOfCat.Field(0 )) }
再次运行,就能修改成功了:
注意 :虽然反射在某些场合下很好用,但反射较损耗性能,因此在性能需求较高和高并发的场景下,应尽量避免使用反射。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 richffan@outlook.com