反射是指在程序运行期间对程序本身进行访问和修改的能力。
反射的应用:当我们用空接口接收不同类型的结构体时,很难用类型断言去判断类型(因为自定义类型可以随意取名),这时就可以用到反射,来来得到结构体的类型,并进一步得到字段和对应值。
实际应用:json
等数据解析(比如结构体博客中JSON序列化与反序列化)/ORM等工具
注意:
- 反射引起的
panic
在编译时不会产生,只有在运行时才能发现,所以代码比较脆弱 - 应用反射的代码通常难以理解
- 反射的性能更低
所以在写代码时要尽量避免使用反射,除非只能使用反射!(这也是我不想写细致内容的借口。。。以后用到再说。。。)
reflect
包提供反射相关功能。
反射细致内容可以参考下面三个文档:
一些可能常用的方法
注意:所有值都默认用静态类型interface{}保存;只有用法,没有理由,具体可以查看参考中的标准库中文文档,或者代码实践。
该空接口类型变量名为data
判断data底层类型
// 判断data的底层类型是否是指针类型
if reflect.TypeOf(data).Kind() != reflect.Ptr {
fmt.Println("data 不是指针类型")
}
其中Kind
方法的返回值是Kind
类型,reflect
包中定义了很多Kind
类型常量代表各种底层类型,Ptr
就是其中一种。
获得指针指向的值的底层类型
reflect.TypeOf(data).Elem().Kind()
其中Elem
方法返回该类型的元素类型,如果该类型的Kind
不是Array、Chan、Map(Map类型的元素是值)、Ptr或Slice,会panic
。
获得结构体字段数量
此时data
的底层类型是结构体。如果底层类型是指针,可以使用前面的Elem
方法得到其值类型。
t := reflect.TypeOf(data)
num := t.NumField() // 此时num的值就是结构体字段数量
其中NumField
方法返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
。
获得结构体第i个字段的类型
t := reflect.TypeOf(data)
field := t.Field(0) // 返回的field是reflect.StructField类型
fieldName := field.Name // 得到字段名
fieldType := field.Type // 得到字段类型,返回值类型是reflect.Type
fieldTag := field.Tag // 得到字段的Tag,返回值类型是reflect.StructTag,该类型有Get(string) string方法,可以获得Tag中键对应的值
Field
方法返回struct
类型的第i
个字段的类型,如非结构体或者i
不在[0, NumField())
内将会panic
。
Field
方法一般和NumField
方法在循环中使用。
根据字段名得到结构体的字段类型
假设structName
是字段名
t := reflect.TypeOf(data)
structField, ok := t.FieldByName(structName)
if !ok {
fmt.Println("字段名不存在")
return
}
structObjType := structField.Type // 得到字段类型