面试官问go反射第一弹
目录
- 反射概念
- reflect包
- 反射类型(Type)和种类(Kind)
- 反射类型(Type)使用
- 反射类型对象(TypeOf)使用
- 结构体对象
- 变量
- 常量
- 指针
- 反射获取结构体的成员类型
- 介绍structField结构
- 演示
- 反射获取结构体标签(Struct Tag)
- 标签概念
- 标签格式
- 标签方法
- 标签演示
- 闲聊
- 【迈莫coding】
反射基本概念
反射让我们能在运行期间探知对象的类型信息和内存结构,这从一定程度弥补了静态语言在动态行为上的不足。同时,反射还是实现元编程的重要手段。
和C数据结构一样,Go对象头部并没有类型指针,通过其自身是无法在运行期间获知任何类型相关信息的。反射操作所需的全部信息都源自接口变量。接口变量除存储自身类型外,还会保存实际对象的类型数据。
Go语言提供了一种机制在运行时更新和检查变量的值,调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型自身作为第一类的值类型处理。
Go语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法,语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。
Go语言提供了 reflect
包来访问程序的反射信息。
reflect包
Go语言中的反射是由 reflect 包提供的,它定义了两个重要类型 Type
和 Value
,分别由 reflect.TypeOf
和 reflect.ValueOf
两个函数获取。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
这两个反射入口函数,会将任何传入的对象转换为接口类型。
在面对类型时,需要区分 Type
和 Kind
。前者表示真实类型(静态类型),后者表示其基础结构(底层结构)类别,接下来,揭开 Type
和 Kind
真面目。
反射类型(Type)和种类(Kind)
在使用反射时,首先需要理解类型(Type
)和种类(Kind
)的含义。在反射中,如果想要区别大品种的类型时,优先使用种类(Kind
),他相比类型(Type
)来说,粒度更细,比如, Map
, Slice
, Chan
都属于引用类型,但如果想区分他们的话,可以使用种类(Kind),因为他们的种类不同,分别为 Map
, Slice
, Chan
。
种类(Kind) 指的是对象归属的品种,在reflect包中有如下定义:
type Kind uint
const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
反射类型(Type)使用
结构体对象
示例:
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
}
func main() {
// 初始化 struct对象
var a = Turbo{}
// 获取a结构体的类型对象
types := reflect.TypeOf(a)
// 获取反射类型对象的名称和种类
fmt.Println(types.Name(), types.Kind())
}
运行结果:
Turbo struct
代码说明:
- 第13行:初始化struct对象a
- 第15行:获取a结构体的类型对象
- 第17行:输出反射类型对象的名称和种类
变量
示例:
package main
import (
"fmt"
"reflect"
)
func main() {
var cost int
types := reflect.TypeOf(cost)
fmt.Println(types.Name(),types.Kind())
}
运行结果:
int int
代码说明:
- 第9行:定义一个int类型的变量a
- 第10行:获取const变量的type类型
- 第11行:输出反射类型对象名称和种类
常量
示例:
package main
import (
"fmt"
"reflect"
)
type Enum int
const (
cost Enum = 1
)
func main() {
types := reflect.TypeOf(cost)
fmt.Println(types.Name(),types.Kind())
}
运行结果:
Enum int
代码说明:
- 第8行:定义一个Enum类型
- 第10行:初始化一个类型为Enum类型的变量cost
- 第13行:获取变量cost的类型对象
- 第14行:输出反射类型对象名称和种类
指针
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。
示例:
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
}
func main() {
// 初始化 struct对象
var a = &Turbo{}
// 获取a结构体的类型对象
types := reflect.TypeOf(a)
if types.Kind() == reflect.Ptr {
elem := types.Elem()
// 获取反射类型对象的名称和种类
fmt.Println(elem.Name(), elem.Kind())
}
}
运行结果:
Turbo struct
代码说明:
- 第8行:定义一个struct类型对象
- 第12行:实例化Turbo结构体对象
- 第13行:获取a结构体的类型对象types
- 第15行:判断types的种类是否为指针
- 第16行:获取指针所指向的类型元素
- 第18行:获取反射类型对象的名称和种类
反射获取结构体的成员类型
Go语言中通过 reflect.TypeOf()
函数获取到类型对象后,如果他的类型为结构体类型,则可以通过反射对象reflect.Type
中的Field()
或者 NumField()
来获取成员变量的属性。具体方法参考下面表格:
通过 Field(i)
方法获取结构体中某个字段的详细情况,由于他的底层是由 StructField 结构体,所以先来了解 StructField
结构体组成架构。
介绍structField结构
StructField
的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
-
Name
:字段名称 -
PkgPath
:字段路径 -
Type
:字段反射类型对象 -
Tag
:字段的结构体标签 -
Offset
:字段在结构体中的相对偏移 -
Index
:Type.FieldByIndex中的返回的索引值 -
Anonymous
:是否为匿名字段
演示
下面举例把通过反射获取结构体成员类型的的相关知识串联起来,形成一个知识体系。
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
Name string
Age int
}
func main() {
var turbo = &Turbo{
Name: "迈莫coding",
Age : 1,
}
types := reflect.TypeOf(turbo)
// 判断是否为指针类型对象
if types.Kind() == reflect.Ptr {
// 通过elem()方法获取指针所指对象
types = types.Elem()
}
for i := 0; i < types.NumField(); i++ {
tf := types.Field(i)
// 获取字段名称
fmt.Printf("字段名称:%v, 字段类型:%v\n" , tf.Name, tf.Type)
// 判断是否为匿名字段
fmt.Printf("字段名称:%v是不是匿名字段?- %v\n", tf.Name, tf.Anonymous)
fmt.Printf("字段名称:%v, 他所在结构体中位置:%v\n", tf.Name, tf.Index)
fmt.Println("--------")
}
if sf, ok := types.FieldByName("Class"); !ok {
fmt.Println("该字段不存在")
}else {
fmt.Printf("该字段名称为:%v", sf.Name)
}
}
运行结果:
字段名称:Name, 字段类型:string
字段名称:Name是不是匿名字段?- false
字段名称:Name, 他所在结构体中位置:[0]
--------
字段名称:Age, 字段类型:int
字段名称:Age是不是匿名字段?- false
字段名称:Age, 他所在结构体中位置:[1]
--------
该字段不存在
反射获取结构体标签(Struct Tag)
结构体标签概念
通过 reflect.Type
获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签( StructTag
)。结构体标签是对结构体字段的额外信息标签。
比如对象关系映射(Object Relational Mapping
,简称 ORM)系统会用到结构体标签。
结构体标签格式
Tag标签在结构体中的存在形式:
type Turbo struct {
Name string `json:name`
Age int `json:age`
}
结构体标签方法
Go语言提供了两种方式来获取某个字段的Tag,一个可以根据Tag中的键获取对应的值,另一个是根据Tag中的键,查询值是否存在。
-
func (tag StructTag) Get(key string) string
:根据 Tag 中的键获取对应的值,例如key1:"value1" key2:"value2"
的 Tag 中,可以传入“key1”获得“value1”。 -
func (tag StructTag) Lookup(key string) (value string, ok bool)
:根据 Tag 中的键,查询值是否存在。
结构体标签演示
package main
import (
"fmt"
"reflect"
)
type Turbo struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
var turbo = &Turbo{
Name: "迈莫coding",
Age: 1,
}
types := reflect.TypeOf(turbo)
// 判断是否为指针类型对象
if types.Kind() == reflect.Ptr {
// 通过elem()方法获取指针所指对象
types = types.Elem()
}
for i := 0; i < types.NumField(); i++ {
tf := types.Field(i)
if tag, ok := tf.Tag.Lookup("json"); ok {
fmt.Printf("字段名称:%v,他的额外约束条件:%v\n", tf.Name, tag)
}
}
for i := 0; i < types.NumField(); i++ {
if tf, ok := types.FieldByName("Name"); ok {
tag := tf.Tag.Get("json")
fmt.Printf("字段名称:%v,他的额外约束条件:%v\n", tf.Name, tag)
}
}
}
结果展示:
字段名称:Name,他的额外约束条件:name
字段名称:Age,他的额外约束条件:age
字段名称:Name,他的额外约束条件:name
字段名称:Name,他的额外约束条件:name
闲聊
- 读完文章,自己是不是和反射的cp率又提高了
- 我是迈莫,欢迎大家和我交流
觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~
文章也会持续更新,可以微信搜索「 迈莫coding 」第一时间阅读。