golang中的反射是由 reflect 包提供支持的,
使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息,下面通过示例来理解获取类型对象的过程:
package main
import (
"fmt"
"reflect"
)
func main() {
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)
Go语言程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。
种类(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 // 底层指针
)
Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。
package main
import (
"fmt"
"reflect"
)
type Enum int
const Zero Enum = 0
func main() {
type cat struct {
}
typeOfCat := reflect.TypeOf(cat{})
fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
typeOfA := reflect.TypeOf(Zero)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
package main
import (
"fmt"
"reflect"
)
func main(){
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) //float64
fmt.Println("kind is float64:", v.Kind() == reflect.Float64) //true
fmt.Println("value:", v.Float()) //3.4
}
package main
import (
"fmt"
"reflect"
)
func main(){
//1.“接口类型变量”=>“反射类型对象”
var circle float64 = 6.28
var icir interface{}
icir = circle
fmt.Println("Reflect : circle.Value = ", reflect.ValueOf(icir)) //Reflect : circle.Value = 6.28
fmt.Println("Reflect : circle.Type = ", reflect.TypeOf(icir)) //Reflect : circle.Type = float64
// 2. “反射类型对象”=>“接口类型变量
v1 := reflect.ValueOf(icir)
fmt.Println(v1) //6.28
fmt.Println(v1.Interface()) //6.28
y := v1.Interface().(float64)
fmt.Println(y) //6.28
// v1.SetFloat(4.13) //panic: reflect: reflect.Value.SetFloat using unaddressable value
//3.修改
fmt.Println(v1.CanSet())//是否可以进行修改 false
v2 := reflect.ValueOf(&circle) // 传递指针才能修改
v4:=v2.Elem()// 传递指针才能修改,获取Elem()才能修改
fmt.Println(v4.CanSet()) //true
v4.SetFloat(3.14)
fmt.Println(circle) //3.14
}
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
}
ins := &cat{} // 创建cat的实例
typeOfCat := reflect.TypeOf(ins) // // 获取结构体实例的反射类型对象
fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
typeOfCat = typeOfCat.Elem() // 取类型的元素
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象 reflect.Type 的 NumField() 和 Field() 方法获得结构体成员的详细信息。
与成员获取相关的 reflect.Type 的方法如下表所示。
reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(StructTag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。
StructField 的结构如下:
type StructField struct {
Name string // 字段名
PkgPath string // 字段路径
Type Type // 字段反射类型对象
Tag StructTag // 字段的结构体标签
Offset uintptr // 字段在结构体中的相对偏移
Index []int // Type.FieldByIndex中的返回的索引值
Anonymous bool // 是否为匿名字段
}
下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。
反射访问结构体成员类型及信息:
package main
import (
"fmt"
"reflect"
)
func main() {
// 声明一个空结构体
type cat struct {
Name string
Type int `json:"type" id:"100"` // 带有结构体tag的字段
}
ins := cat{Name: "mimi", Type: 1} // 创建cat的实例
typeOfCat := reflect.TypeOf(ins) // 获取结构体实例的反射类型对象
for i := 0; i < typeOfCat.NumField(); i++ { // 遍历结构体所有成员
fieldType := typeOfCat.Field(i) // 获取每个成员的结构体字段类型
fmt.Printf("name: %v tag: '%v'\n", fieldType.Name, fieldType.Tag) // 输出成员名和tag
}
if catType, ok := typeOfCat.FieldByName("Type"); ok { // 通过字段名, 找到字段类型信息
fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id")) // 从tag中取出需要的tag
}
}
通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(StructTag)。结构体标签是对结构体字段的额外信息标签。
JSON、BSON 等格式进行序列化及对象关系映射(Object Relational Mapping,简称 ORM)系统都会用到结构体标签,这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的,无须实例化结构体,可以通过反射获取到。
Tag 在结构体字段后方书写的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成;键与值使用冒号分隔,值用双引号括起来;键值对之间使用一个空格分隔。
StructTag 拥有一些方法,可以进行 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 中的键,查询值是否存在。编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,示例代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
type cat struct {
Name string
Type int `json: "type" id:"100"`
}
typeOfCat := reflect.TypeOf(cat{})
if catType, ok := typeOfCat.FieldByName("Type"); ok {
fmt.Println(catType.Tag.Get("json"))
}
}
运行上面的代码会输出一个空字符串,并不会输出期望的 type。
代码第 11 行中,在 json: 和 “type” 之间增加了一个空格,这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。这个错误在开发中非常容易被疏忽,造成难以察觉的错误。所以将第 12 行代码修改为下面的样子,则可以正常打印。
type cat struct {
Name string
Type int `json:"type" id:"100"`
}
package main
import (
"fmt"
"reflect"
)
type Animal struct {
Name string
Age int
}
type Cat struct {
Animal
Color string
}
func main(){
c1 := Cat{Animal{"猫咪", 1}, "白色"}
t1 := reflect.TypeOf(c1)
for i:=0;i<t1.NumField();i++{
fmt.Println("Field(", i , "): ", t1.Field(i))
}
// FiledByIndex()的参数是一个切片,第一个数是Animal字段,第二个参数是Animal的第一个字段
f1:=t1.FieldByIndex([]int{0,0})
f2:=t1.FieldByIndex([]int{0,1})
fmt.Println("f1: ", f1)
fmt.Println("f2: ",f2)
v1:=reflect.ValueOf(c1)
fmt.Println(v1.Field(0)) //{猫咪 1}
fmt.Println(v1.FieldByIndex([]int{0,0})) //猫咪
}
package main
import (
"reflect"
"fmt"
)
type Student struct {
Name string
Age int
School string
}
func main() {
/*
修改内容
*/
s1:= Student{"王二狗",18,"清华大学"}
v1 := reflect.ValueOf(&s1)
if v1.Kind() ==reflect.Ptr && v1.Elem().CanSet(){
v1 = v1.Elem()
fmt.Println("可以修改。。")
}
f1:=v1.FieldByName("Name")
fmt.Println(f1.CanSet())
f1.SetString("王三狗")
f2:=v1.FieldByName("Age")
fmt.Println(f2.CanSet())
f2.SetInt(20)
fmt.Println(s1)
}
package main
import (
"fmt"
"reflect"
)
//1.提供一个结构体
type Person struct {
Name string
Age int
Sex string
}
//2.提供一个方法
func (p Person) Say(msg string) {
fmt.Println("Hello..", msg)
}
func (p Person) PrintInfo() {
fmt.Println("姓名:",p.Name,"年龄:",p.Age,"性别:",p.Sex)
}
func main(){
p1:=Person{"王二狗",30,"男"}
// 获取对象的类型
t1 := reflect.TypeOf(p1)
fmt.Println(t1, t1.Name(), t1.Kind()) //main.Person Person struct
fmt.Println(t1.NumField(), t1.NumMethod()); //3个字段,2个方法
//获取值,如果是结构体类型,获取的是字段的值
v1:=reflect.ValueOf(p1)
fmt.Println(v1) //{王二狗 30 男}
if t1.Kind() == reflect.Struct{
for i:=0;i<t1.NumField();i++{
field := t1.Field(i)
fmt.Printf("字段名字:%s,字段类型:%s,字段数值:%v\n",field.Name,field.Type,v1.Field(i).Interface()) //通过interface方法来取出这个字段所对应的值
}
}
//2.操作方法
for i:=0;i<t1.NumMethod();i++{
m:=t1.Method(i)
fmt.Println(m.Name,m.Type)
/*
PrintInfo func(main.Person)
Say func(main.Person, string)
*/
}
m2:=v1.MethodByName("PrintInfo")
m2.Call(nil) //姓名: 王二狗 年龄: 30 性别: 男
m1 := v1.MethodByName("Say")
args:=[]reflect.Value{reflect.ValueOf("干啥呢?")}
m1.Call(args)
}