背景介绍

Go中的反射使用的就是官方提供的reflect这个包

使用方法

类型reflect.Type

主要是使用reflect.Typeof()获取到变量的类型信息(reflect.Type),再通过里面一些方法拿到想要的属性

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
//a/a.go
package a

type A struct {
Name string
}

//main.go
package main

import (
"fmt"
"reflect"

"./a"
)

func main() {
a := a.A{
Name: "hello reflect",
}
v := reflect.TypeOf(a)
fmt.Println("string\t", v.String()) //全名称:[包名.]类型名
fmt.Println("kind\t", v.Kind()) //类型
fmt.Println("pkgpath\t", v.PkgPath()) //包名 包名可能为空
fmt.Println("name\t", v.Name()) //类型名 可能为空
fmt.Println("comparable\t", v.Comparable()) //是否可以比较
fmt.Println("size\t", v.Size()) // 占用字节
fmt.Println("align\t", v.Align()) // 对齐字节 一般没啥用
}

// output
string a.A
kind struct
pkgpath .../a //省略了前面的路径
name A
comparable true
size 16
align 8

reflect.Type的方法很多,但是可不可以使用取决于变量本身的类型,比如Len()方法string是使用不了的,这里整理了一个表格供大家参考

示例代码位于:

类型 可用方法列表 用途
通用 String 全名称:[包名.]类型名
Kind 类型
Name 类型名 可能为空
PkgPath 包名 包名可能为空
Comparable 是否可以比较
Size 占用字节
Align 对齐字节
Struct FieldAlign 字段内存对齐字节
NumField 字段数
Field 根据索引获取字段
FieldByName 根据名称获取字段
NumMethod 导出的方法数
Method 根据索引获取方法,只能访问导出的方法(大写的)
MethodByName 根据名称获取方法,只能访问导出的方法(大写的)
FieldByIndex 当struct是嵌套的,这个方法可以找到内嵌的字段,第0个字段的第0个字段
FieldByNameFunc 根据所给的方法查找,如果多个匹配返回第一个匹配的
Func NumIn 入参个数
NumOut 返回值个数
In 获取入参索引位置的类型
Out 获取返回值索引位置的类型
IsVariadic 是否有可变参数 比如… float64
Channel ChanDir channel方向
Array Len 长度
Slice
Map Key key的类型
Array,Slice,Channel,Map,Pinter Elem 内部元素的类型
数字类型 Bits 占用bit数

还有几个特殊方法

AssignableTo(v interface{})

判断能不能把v赋值给自己

ConvertibleTo(v interface{})

判断能不能和v进行比较

Implements(v interface{})

v只能传接口,判断有没有实现这个接口,使用起来会比较复杂,下面给出示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"reflect"
)

type Empty interface {
}

func main() {
a := 1
v := reflect.TypeOf(a)
fmt.Println(v.Implements(reflect.TypeOf((*Empty)(nil)).Elem()))// true
fmt.Println(v.Implements(reflect.TypeOf((*Test)(nil)).Elem()))// false
}

reflect.Value

上文所有的操作都是和类型相关的,那么如何对实际的值进行操作呢?

首先得明确一点,那就是Go的参数传递都是值传递的,所以想要修改本身,得传递自身的指针.

1
2
3
4
5
6
7
8
func main() {
a := 1
v := reflect.ValueOf(&a)
fmt.Println(v.CanSet())// false
fmt.Println(v.Elem().CanSet())// true
v.Elem().Set(reflect.ValueOf(5))
fmt.Println(a)// 5
}

这里可以看到,v本身是不能修改的,而v.Elem()v的元素是可以修改的,这是什么意思呢?其实就是 指针的指向是不能修改的,但是指针指向的值可以修改的。

同样,下面整理了一份表格以供查阅

示例代码位于:

原理剖析

进阶理解