go学习-反射
Posted 懒佯佯大哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go学习-反射相关的知识,希望对你有一定的参考价值。
介绍
-
反射是一种机制,是指可以动态的获取对象信息(名称、属性、类型等)、操作对象(创建对象、修改属性值、调用方法等)
-
意义:
- 因为有的时候,并不知道对象具体是什么类型,有哪些属性和方法
- 便于编写通用的框架,实现松耦合、高复用。比如,ORM库操作、json序列化等
-
golang的反射
- golang提供了reflect包实现反射处理,golang的反射是对接口变量的动态类型(type)和动态值(value)相关的操作。
-
reflect包的核心如下:
- reflect.TypeOf(i interface) Type:获取一个“空接口”的动态类型,封装成reflect.Type返回(内部为reflect.rType实例)(备注:入参为副本拷贝)
- reflect.ValueOf(i interface) Value:获取一个“空接口”的所有信息(备注:入参为副本拷贝)
- Value:是一个结构体,拥有“动态对象”的所有信息
- Type:这是一个接口
- 此外,还有2个包,可以了解一下:
* Method:定义了方法的属性名而已
* Kind:定义了元数据
-
图示:
示例介绍
入门示例
- 通过反射操作一个int:
func main()
// 基本数据类型的反射操作
var i int = 10
fmt.Println("获取i的Type:", reflect.TypeOf(i)) // 获取反射类型
fmt.Println("获取i的Value:", reflect.ValueOf(i)) // 获取反射值
// 开始修改i的值,需要用指针,因为ValueOf的入参为值拷贝
iOf := reflect.ValueOf(&i)
iOf.Elem().SetInt(20)
fmt.Println("修改后i的值为:", i)
// 将Value再转回指针:
i2 := iOf.Interface().(*int)
fmt.Println("修改后i2的值为:", *i2)
- 输出结果:
获取i的Type: int
获取i的Value: 10
修改后i的值为: 20
修改后i2的值为: 20
这是一个简单的示例,可以看到,通过反射,正常的修改了i的值。
并且,反射后的Value,也可以正常的转换回int指针
Value操作
- 上面的简单的反射操作,核心在于Value类的操作,而Value包含了很多方法(http://docscn.studygolang.com/pkg/reflect/#Copy)
// 设置Value值:
//func (v Value) Set(x Value) // 设置一个Value值,比如int类型的:i.Set(reflect.ValueOf(44))
//func (v Value) SetBool(x bool) // 设置bool值,如下都是相同的调用方式:i.Elem().SetBool(false)
//func (v Value) SetBytes(x []byte)
//func (v Value) SetCap(n int)
//func (v Value) SetComplex(x complex128)
//func (v Value) SetFloat(x float64)
//func (v Value) SetInt(x int64)
//func (v Value) SetLen(n int)
//func (v Value) SetMapIndex(key, val Value)
//func (v Value) SetPointer(x unsafe.Pointer)
//func (v Value) SetString(x string)
//func (v Value) SetUint(x uint64)
fmt.Println("Addr():", iOf.Elem().Addr())
fmt.Println("Bool():", reflect.ValueOf(false).Bool())
fmt.Println("Bytes():", reflect.ValueOf(make([]byte, 3)).Bytes())
//func (v Value) Addr() Value // 获取地址信息:iOf.Elem().Addr())
//func (v Value) Bool() bool // 获取bool型的反射:reflect.ValueOf(false).Bool()
//func (v Value) Bytes() []byte // 获取字节数组: reflect.ValueOf(make([]byte, 3)).Bytes()
//func (v Value) Call(in []Value) []Value
//func (v Value) CallSlice(in []Value) []Value
fmt.Println("CanAddr():", reflect.ValueOf(1).CanAddr())
fmt.Println("CanInterface():", reflect.ValueOf(1).CanInterface())
fmt.Println("CanSet():", reflect.ValueOf(1).CanSet())
//func (v Value) CanAddr() bool // 是否可以获取Addr:reflect.ValueOf(1).CanAddr()
//func (v Value) CanInterface() bool // 是否可以转化为Interface:reflect.ValueOf(1).CanInterface()
//func (v Value) CanSet() bool // 是否可以修改值:reflect.ValueOf(1).CanSet()
//func (v Value) Cap() int // 获取容量值:如果是非数组、管道、切片,则会异常panic
//func (v Value) Close() // 关闭channel对象,非channel时抛出异常panic
//func (v Value) Complex() complex128 // 返回复数的underlying value(实部?)
//func (v Value) Convert(t Type) Value // 将v类型转为t类型
//func (v Value) Elem() Value // 返回v的值、或v指向的值(指针场景)
//func (v Value) Field(i int) Value // 返回struct的第i个字段
//func (v Value) FieldByIndex(index []int) Value // 返回一个嵌套的field列表
//func (v Value) FieldByName(name string) Value // 根据名字在struct中查找字段
//func (v Value) FieldByNameFunc(match func(string) bool) Value // 按照函数规则查找字段(函数式编程)
var f1 float64 = 1.234
fmt.Println("f1 Float(): ", reflect.ValueOf(f1).Float())
//func (v Value) Float() float64 // 返回float64 Value的真实值
//func (v Value) Index(i int) Value // 返回第i个Value,仅限于:数组、管道、切片
//func (v Value) Int() int64 // 返回int值,仅限于int类型:int8/16/32/64
//func (v Value) Interface() (i interface)// 将Value转为interface
//func (v Value) InterfaceData() [2]uintptr // 没理解????
//func (v Value) IsNil() bool // 判断Value是否为nil
//func (v Value) IsValid() bool // 判断Value是否有值,如果为0时返回false
//func (v Value) Kind() Kind // 返回kind类型
//func (v Value) Len() int // 返回Value长度,仅限于string、数组、管道、切片
//func (v Value) MapIndex(key Value) Value // 返回map类型的key对应的value
//func (v Value) MapKeys() []Value // 返回map类型的key列表
//func (v Value) Method(i int) Value // 返回第i个方法(struct中方法按照字母的字典序排序)
//func (v Value) MethodByName(name string) Value // 按照name查找方法
//func (v Value) NumField() int // 返回字段数
//func (v Value) NumMethod() int // 返回方法数
//func (v Value) OverflowComplex(x complex128) bool // 判断Value是否可以表示x complex128
//func (v Value) OverflowFloat(x float64) bool // 判断Value是否可以表示x float64
//func (v Value) OverflowInt(x int64) bool // 判断Value是否可以表示x int64
//func (v Value) OverflowUint(x uint64) bool // 判断Value是否可以表示x uint64
//func (v Value) Pointer() uintptr // 返回v的值,将v的值作为指针
//func (v Value) Recv() (x Value, ok bool) // ???
//func (v Value) Send(x Value) // ???
//func (v Value) Slice(i, j int) Value // 返回v中到j的切片
//func (v Value) Slice3(i, j, k int) Value // 返回v的三维的切片???
//func (v Value) String() string // 返回v的值为string
//func (v Value) TryRecv() (x Value, ok bool) // ???
//func (v Value) TrySend(x Value) bool // ??/
//func (v Value) Type() Type // 返回v的类型
//func (v Value) Uint() uint64 // 返回uint64的value值
//func (v Value) UnsafeAddr() uintptr // 返回指向v的的值的指针---注意和Pointer()的区别
备注:方法里面含有一些channel的操作,这里先不做介绍
示例:操作struct
- Value包含了很多方法,可以通过一个简单的struct操作来学习一下
- 声明一个Student struct:
// 待反射的类,定义三个属性、三个方法
type Student struct
Name string `json:"name" nickname:"name"`
Age int `json:"age" nickname:"old"`
Place *string `json:"place" nickname:"pla"`
func (s Student) Say()
fmt.Println("Student say... ...")
func (s Student) Play(a string, b string)
fmt.Println("Student play with ", a, b)
func (s Student) Cal(a int, b int) int
fmt.Println("Student cal a+b=", a+b)
return a + b
- 测试:
func main()
fmt.Println("===========================================================")
var str string = "杭州"
student := Student
Name: "zhangsan",
Age: 20,
Place: &str,
fmt.Println("获取的Student类型:", reflect.TypeOf(student))
fmt.Println("获取的Student值为:", reflect.ValueOf(student))
fmt.Println("==========================开始获取student的属性方法==========================")
sOf := reflect.ValueOf(student)
sTy := reflect.TypeOf(student)
fmt.Println("Student的字段个数:", sOf.NumField())
fmt.Println("Student的方法个数:", sOf.NumMethod())
for i:=0; i<sOf.NumField(); i++
fmt.Println("Student的第", i ,"个字段为:", sOf.Field(i))
fmt.Println("Student的第", i ,"个方法为:", sOf.Method(i))
get := sTy.Field(i).Tag.Get("json")
if get != ""
fmt.Println("Student的第", i ,"个字段的tag为:", get)
fmt.Println("==========================修改值==========================")
// 下面两种方式都可以
//reflect.ValueOf(&student).Elem().FieldByName("Name").SetString("lisi")
reflect.ValueOf(&student).Elem().Field(0).SetString("lisi")
fmt.Println("Student修改后:", student)
fmt.Println("==========================反射调用方法==========================")
fmt.Println("调用方法:", sOf.MethodByName("Say").Call(nil))
var params []reflect.Value
val1 := reflect.ValueOf(2)
val2 := reflect.ValueOf(3)
params = append(params, val1)
params = append(params, val2)
fmt.Println("调用方法:", sOf.MethodByName("Cal").Call(params))
- 输出结果:
===========================================================
获取的Student类型: main.Student
获取的Student值为: zhangsan 20 0xc000010200
==========================开始获取student的属性方法==========================
Student的字段个数: 3
Student的方法个数: 3
Student的第 0 个字段为: zhangsan
Student的第 0 个方法为: 0x1087960
Student的第 0 个字段的tag为: name
Student的第 1 个字段为: 20
Student的第 1 个方法为: 0x1087960
Student的第 1 个字段的tag为: age
Student的第 2 个字段为: 0xc000010200
Student的第 2 个方法为: 0x1087960
Student的第 2 个字段的tag为: place
==========================修改值==========================
Student修改后: lisi 20 0xc000010200
==========================反射调用方法==========================
Student say... ...
调用方法: []
Student cal a+b= 5
调用方法: [<int Value>]
注意,当需要修改结构体里的字段时,需要传入指针类型
通过反射创建结构体
- 代码:
fmt.Println("==========================反射创建Student==========================")
stuType := reflect.TypeOf(student)
value := reflect.New(stuType)
student1 := value.Interface().(*Student)
student1.Name = "fanshe"
student1.Age = 200
fmt.Println("反射创建的student", value)
- 结果
可以看到,正常的创建了Student类
==========================反射创建Student==========================
反射创建的student &fanshe 200 <nil>
其它
- 关于reflect.Kind
它是一个常量类集合:
const (
Invalid Kind = iota // iota是一个常量0,用于const声明中,表示序列0,后续的每一行数会加一
Bool // 1
Int // 2
Int8 // 3 因为iota标识了序列起始为0,故下面的每一行都+1
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
这里有一点需要解释一下:iota
a. 在const中声明一次iota即可,后续的声明都会被覆盖掉
b. iota声明是以行为标识,一行中声明多个变量时,这些变量都是一样的结果
- 测试一下iota:
fmt.Println("==========================测试iota==========================")
const (
a = iota
b
c
d string = "ssss"
e
)
fmt.Printf("a=%v, b=%v, c=%v, d=%v, e=%v \\n", a, b, c, d, e)
const (
a1 = iota
b11, b12 = iota, iota
c1 = iota
)
fmt.Printf("a1=%v, b11=%v, b12=%v, c1=%v \\n", a1, b11, b12, c1)
#########################################
//输出结果:
==========================测试iota==========================
a=0, b=1, c=2, d=ssss, e=ssss
a1=0, b11=1, b12=1, c1=2
可以看到:第二个声明的b11和b12在同一行,并且值都为1(本身声明的iota会被覆盖掉)
- reflect.Method
Method是一个结构体,暂无对外暴露的成员方法(在反射的内部调用)
type Method struct
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
- Type和Kind的区别:
Type是类型,Kind是类别:Kind的范围大于Type:对于基本类型(int/float/string…),Kind和Type返回一样,但是对于struct,Kind返回“struct”,而Type返回具体的结构体名“Student”
参考
https://zhuanlan.zhihu.com/p/53114706
https://www.jianshu.com/p/9816a7a551cd
https://www.jianshu.com/p/32e4cf8ffffb
https://www.jianshu.com/p/444b55edf32e
https://zhuanlan.zhihu.com/p/53114706
以上是关于go学习-反射的主要内容,如果未能解决你的问题,请参考以下文章
go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 什么是反射?
go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 使用反射获取结构体的成员变量类型
go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 指针和指针指向的元素
go语言学习笔记 — 进阶 — 反射:反射的类型对象(reflect.Type)— 反射类型对象的类型名(Type)和种类(Kind)