GO反射

一、反射介绍

  1. 反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们
  2. Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

二、反射中的Type和Kind

2.1 Type Type.Name()

type返回的是当前被反射对象的实际变量类型,如:

1
2
3
4
var name string = "abcd"
typeof := reflect.typeof()
//实际变量类型
typeof.Name()

在GO中type实际上指得是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称

2.2 Kind Type.Kind()

通俗意义上面来讲,Kind是GO中定义的一个对象属性的归属,用户可能会通过type关键词给类型起别名,而这个时候kind就反映了它实际真实的对象归属。
GO中内置的Kind类如下所示,其实它是一个枚举类型

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
31
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 // 底层指针
)

如:

1
2
3
4
5
6
7
8
9
10
11
12
type Stu struct{
}

var stu Stu = Stu{}

func main(){
reflectStu := reflect.Typeof(stu)
//真实当前变量名,面具名称 -> Stu
reflectStu.Name()
//真是所属Go中的哪个类型. -> struct
reflectStu.Kind()
}

2.3、TypeOf() && ValueOf()

#todo

  • TypeOf() && ValueOf() 区别
  1. 获取实际的Type类型,Type类型包含两部分一部分是实际类型(Name),一部分是所属GO中的类型(Kind)

三、内置Pair键值对

GO中的变量实际上分为两个部分,一个部分是变量类型,一个则是实际的Value。

  • type:类型
    • static type:静态类型,指当前已经确定的类型
    • concrete type:动态类型,指在运行期间动态确定的值,比如一个interface的变量值为8.0,但是实际上在运行期间会确定为float。这个就是动态类型。
  • value:指当前变量实际的值

四、reflect包

4.1、reflect.Type(通用方法所有类型均可使用)

4.1.1、Align() int && FieldAlign() int

  1. Align() int
    返回该变量实际上在内存中分配的字节数
  2. FieldAlign() int
    返回该类型在结构体中作为字段的字节数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main  

import (
"fmt"
"reflect")

type User struct {
name string
}

func (stu User) GetName() {
fmt.Println("name is :", stu.name)
}

func main() {
user := User{name: "zhangsan"}
reflectUser := reflect.TypeOf(user)
fmt.Println("内存中分配的字节数为:", reflectUser.Align())
fmt.Println("内存中作为成员变量分配的字节数为:", reflectUser.FieldAlign())
}

4.1.2、Method(int) int && MethodByName(string) (Method,bool)

  1. Method(int) int
    1. Method 这个方法返回类型方法集中的第 i 个方法。
    2. 如果 i 不在[0, NumMethod()]范围内,就会 panic
    3. 对于一个**非接口类型 T 或 *T,返回的 Method 的 Type 和 Func**。
    4. fields 字段描述一个函数,它的第一个参数是接收方,而且只有导出的方法可以访问。
    5. 对于一个接口类型返回的 Method 的 Type 字段给出的是方法签名没有接收者,Func字段为nil。
    6. 方法是按字典序顺序排列的。
  2. NumMethod()
    1. NumMethod 返回使用 Method 可以访问的方法数量。
    2. NumMethod 只在接口类型的调用的时候,会对未导出方法进行计数
  3. MethodByName(string) (Method,bool)
    1. MethodByName 返回类型中带有该名称的方法。
    2. 方法集和一个表示是否找到该方法的布尔值。
    3. 对于一个非接口类型 T 或 *T,返回的 Method 的 Type 和 Func。
    4. fields 字段描述一个函数,其第一个参数是接收方。
    5. 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。

示例代码:

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
package main  

import (
"fmt"
"reflect")

type User struct {
name string
}

func (stu User) GetName() {
fmt.Println("name is :", stu.name)
}

func main() {
user := User{name: "zhangsan"}
//返回暴露的方法数量
fmt.Println(reflect.TypeOf(user).NumMethod())
//输出第0个方法
method := reflect.TypeOf(user).Method(0)
//使用方法名称获取method
method = reflect.TypeOf(user).MethodName(0)
fmt.Printf("method is : %v\n", method)
fmt.Printf("method Type is : %v\n method name is: %v\n method pkg is : %v\n method is : %v\n", method.Type, method.Name, method.PkgPath, method.Func)
}

4.1.3、PkgPath() string

  1. PkgPath 返回一个定义类型的包的路径,也就是导入路径,导入路径是唯一标识包的类型,如 “encoding/base64”。
  2. 如果类型是预先声明的(string, error)或者没有定义(T, struct{}, []int,或 A,其中 A 是一个非定义类型的别名),包的路径将是空字符串。(这里指匿名情况返回包路径为空“”)*

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main  

import (
"fmt"
"reflect")

type User struct {
name string
}

func (stu User) GetName() {
fmt.Println("name is :", stu.name)
}

func main() {
user := User{name: "zhangsan"}
reflectUser := reflect.TypeOf(user)
fmt.Println("user 对象的包路径是", reflectUser.PkgPath())
temp := struct {
id int
}{}
reflectTemp := reflect.TypeOf(temp)
fmt.Println("temp 对象的包路径是", reflectTemp.PkgPath())
}

4.1.4、Size() uintptr

返回存储给定存储类型的值所需要的字节数量

1
2
3
4
5
6
7
func main() {  
temp := struct {
id int
}{}
reflectTemp := reflect.TypeOf(temp)
fmt.Println("存储temp对象所需要的存储空间大小是", reflectTemp.Size())
}

4.1.5、String() string

返回该类型的字符串表示(打印对象全部字段)

1
2
3
4
5
6
7
func main() {  
temp := struct {
id int
}{}
reflectTemp := reflect.TypeOf(temp)
fmt.Println("temp对象字符串表示是:", reflectTemp.String())
}

4.1.6、类型比较

  1. Implements(u Type) bool
    Implements 表示该类型是否实现了接口类型 u。
  2. AssignableTo(u Type) bool
    AssignableTo 表示该类型的值是否可以分配给类型 u。
  3. ConvertibleTo(u Type) bool
    ConvertibleTo 表示该类型的值是否可转换为 u 类型。
  4. Comparable() bool
    Comparable 表示该类型的值是否具有可比性。

4.2、reflect.Type 专有方法

4.2.1、BIts() int

返回类型单位大小,类型非数字类型发生Panic

4.2.1、ChanDir() ChanDir

返回通道类型的方向,不是的话会Panic

4.2.2、IsVariadic() bool

返回当前是不是一个包含可变参数语法糖的函数

  1. t表示一个函数 func(x int, y … float64)
  2. t.NumIn() == 2
  3. t.In(0)是 “int” 的 reflect.Type 反射类型。
  4. t.In(1)是 “[]float64” 的 reflect.Type 反射类型。
  5. t.IsVariadic() == true
  6. 如果类型的 Kind 不是 Func.IsVariadic,IsVariadic 会 panic

4.2.3、Elem()

获取指针指向的对象的类型,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Stu struct {  
}

var stuPtr *Stu = &Stu{}

func main() {
reflectStu := reflect.TypeOf(stuPtr)
//真实当前变量名,面具名称 ->
fmt.Println("当前变量类型的type类型:", reflectStu.Name())
//真是所属Go中的哪个类型.Kind -> Ptr
fmt.Println("当前变量类型的Kind类型:", reflectStu.Kind())

//获取指针所指向的类型
reflectElem := reflectStu.Elem()
//指针指向类型的type. -> Stu
fmt.Println("指针指向的类型的type类型:", reflectElem.Name())
//指针指向的类型的Kind -> struct
fmt.Println("指针指向的类型的Kind类型:", reflectElem.Kind())
}

4.2.4、Fileld(i int) StructField && FieldByIndex(index []int) StructField && FieldByName(name string) (StructField, bool) && FieldByNameFunc(match func(string) bool) (StructField, bool)

  1. Fileld(i int) StructField
    1. Field 返回一个结构类型的第 i 个字段。
    2. 如果类型的 Kind 不是 Struct,就会 panic。
    3. 如果 i 不在 [0, NumField()] 范围内,也会 panic。
  2. FieldByIndex(index []int) StructField
    1. FieldByIndex 返回索引序列对应的嵌套字段。它相当于对每一个 index 调用 Field。
    2. 如果类型的 Kind 不是 Struct,就会 panic。
  3. FieldByName(name string) (StructField, bool)
    1. FieldByName 返回给定名称的结构字段和一个表示是否找到该字段的布尔值。
  4. FieldByNameFunc(match func(string) bool) (StructField, bool)
    1. FieldByNameFunc 返回一个能满足 match 函数的带有名称的 field 字段。布尔值表示是否找到。
    2. FieldByNameFunc 先在自己的结构体的字段里面查找,然后在任何嵌入结构中的字段中查找,按广度第一顺序搜索。最终停止在含有一个或多个能满足 match 函数的结构体中。如果在该深度上满足条件的有多个字段,这些字段相互取消,并且 FieldByNameFunc 返回没有匹配。
    3. 这种行为反映了 Go 在包含嵌入式字段的结构的情况下对名称查找的处理方式

typeUser.NumField()

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
31
32
33
34
35
36
37
38
39
40
41
42
43

package main

import (
"fmt"
"reflect")

type User struct {
name string
id uint
family Family
}

type Family struct {
location string
familyNumber int
familyName string
}

func main() {
user := User{
name: "小明",
id: 1132,
family: Family{
location: "西安",
familyNumber: 11,
familyName: "小明的家",
}, } typeUser := reflect.TypeOf(user)
fmt.Printf("当前变量含有: %v 个成员变量\n", typeUser.NumField())
fmt.Printf("成员变量第一个:%v\n", typeUser.Field(1).Name)
//索引位置变量是非结构体会导致Panic
fmt.Printf("返回一个指定索引切片获得的多个Field组成的结构体:%v\n", typeUser.FieldByIndex([]int{2}))
name, b := typeUser.FieldByName("name")
fmt.Printf("返回指定结构体字段名称的Field对象:%v 以及,是否找到该对象:%v\n", name, b)
//函数目前不能递归搜索
nameFunc, b2 := typeUser.FieldByNameFunc(func(s string) bool {
if s == "familyNumber" {
return true
} else {
return false
}
}) fmt.Printf("返回指定满足匹配函数名称的Field对象:%v 以及,是否找到该对象:%v\n", nameFunc, b2)
}

4.2.5、In(i int) Type –Func && Out(i int) Type –Func.Out

In 返回函数类型的第 i 个输入参数的类型。

  1. 如果类型的 Kind 不是 Func 类型会 panic。
  2. 如果 i 不在 [0,NumIn())范围内会出现Panic
1
2
3
4
5
6
7
func main() {  
funcReflect := func(name string, id int) (string, int) {
return "小明", 12
}
println("输出函数的第一个参数类型是:", reflect.TypeOf(funcReflect).In(0).Name())

}

Out(i int) Type –Func.Out

  1. Out 返回一个函数类型的第 i 个输出参数的类型。
  2. 如果类型的类型不是 Func.Out,会 panic。
  3. 如果 i 不在 [0, NumOut()) 的范围内,会 panic。
1
2
3
4
5
6
func main() {  
funcReflect := func(name string, id int) (string, int) {
return "小明", 12
}
println("输出函数的第一个参数是:", reflect.TypeOf(funcReflect).Out(1).Name())
}

4.2.6、Key() Type –Map

  1. Key 返回一个 map 类型的 key 类型。
  2. 如果类型的 Kind 不是 Map,会 panic。
1
2
3
4
5
func main() {  
var mapTemp map[string]string = make(map[string]string)
print("map类型的key的类型是", reflect.TypeOf(mapTemp).Key().Name())
print("非map类型的keys是,会发生Panic:", reflect.TypeOf(8).Key().Name())
}

4.2.7、Len() int –Arrary

  1. Len 返回一个数组类型的长度。
  2. 如果类型的 Kind 不是 Array,会 panic。
1
2
3
4
func main() {  
var array = [...]int{0, 1, 2, 3}
print("输出的数组长度是:", reflect.TypeOf(array).Len())
}

4.2.8、NumField() int –Struct

  1. NumField 返回一个结构类型的字段数目。
  2. 如果类型的 Kind 不是 Struct,会 panic。

示例如下:[[#4 2 4、Fileld i int StructField FieldByIndex index int StructField FieldByName name string StructField bool FieldByNameFunc match func string bool StructField bool]]

4.2.9、NumIn() int –Func.NumIn() && NumOut() int –Func.NumOut()

  1. NumIn() int –Func.NumIn()

    1. NumIn 返回一个函数类型的输入参数数。
    2. 如果类型的 Kind 不是Func.NumIn(),会 panic。
  2. NumOut() int –Func.NumOut()

    1. NumOut 返回一个函数类型的输出参数数。
    2. 如果类型的 Kind 不是 Func.NumOut(),会 panic。
1
2
3
4
5
6
7
8
func main() {  
funcReflect := func(name string, id int) (string, int) {
return "小明", 12
}
println("输出函数的参数个数是:", reflect.TypeOf(funcReflect).NumIn())
println("输出函数的返回值个数是:", reflect.TypeOf(funcReflect).NumOut())

}

4.2、reflect.Value

特殊说明的是 zero Value,zero Value 代表没有值。它的 IsValid() 方法返回 false,Kind() 方法返回 Invalid,String() 方法返回 “”,而剩下的所有其他方法均会 panic。

#todo