一、概述
每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。
在讲反射之前,先来看看Golang关于类型设计的一些原则
-
变量包括(type, value)两部分
- 理解这一点就知道为什么nil != nil了
-
type 包括 static(静态)type和concrete(具体)type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型
-
类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.
接下来要讲的反射,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:
(value, type)
value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
var r io.Reader
r = tty
接口变量r的pair中将记录如下信息:(tty, *os.File),这个pair在接口变量的连续赋值过程中是不变的,将接口变量r赋给另一个接口变量w:
var w io.Writer
w = r.(io.Writer)
接口变量w的pair与r的pair相同,都是:(tty, *os.File),即使w是空接口类型,pair也是不变的。
interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。
二、TypeOf
func TypeOf(i interface{}) Type
package main
import (
"fmt"
"reflect"
)
type MyStu struct {
Name string
Age int
Sex bool
}
func (m MyStu) Read(p []byte) (n int, err error) {
fmt.Println("myStu::Read()")
return 0,nil
}
/**
TypeOf
*/
func main() {
stu := MyStu{"jadeshu", 12, true}
// 获取stu接口的类型
t := reflect.TypeOf(stu)
fmt.Println(t.Name())
// 获取该接口类型的字段个数
count := t.NumField()
for i := 0; i < count; i++ {
// 获取接口内具体一个字段结构体
field := t.Field(i)
fmt.Printf("字段名:%s---字段类型:%v\r\n", field.Name, field.Type)
}
}
运行结果:
三、ValueOf
func ValueOf(i interface{}) Value
package main
import (
"fmt"
"reflect"
)
type myStu struct {
//sex bool // 注意这里是私有字段
Name string
Age int
Sex bool
}
func (m myStu) Read(p []byte) (n int, err error) {
fmt.Println("myStu::Read()")
return 0,nil
}
/**
ValueOf
*/
func main() {
stu := myStu{"jadeshu", 12, true}
// 获取接口的值
v := reflect.ValueOf(stu)
// 等同fmt.Println(v.Interface())
fmt.Println(v)
// 获取该接口类型的字段个数
count := v.NumField()
for i := 0; i < count; i++ {
// 获取字段对应的值
vtmp := v.Field(i).Interface()
fmt.Printf("值为:%v\r\n", vtmp)
}
}
运行结果:
四、获取父类字段的类型和值
Value方法
- func (v Value) FieldByIndex(index []int) Value
- func (v Value) FieldByName(name string) Value
- func (v Value) FieldByNameFunc(match func(string) bool) Value
Type接口的方法:
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
package main
import (
"fmt"
"reflect"
)
type Base struct {
Year int
hours int
}
type Dirve struct {
Base
Name string
}
/**
ValueOf
*/
func main() {
d := Dirve{Base{2019, 12},"jadeshu"}
// 获取类型和值
t := reflect.TypeOf(d)
v := reflect.ValueOf(d)
// 等同fmt.Println(v.Interface())
fmt.Println(v)
// 获取父类字段Base的字段的类型
bt := t.FieldByIndex([]int{0,0})
fmt.Printf("名称:%v; 类型:%v; ", bt.Name ,bt.Type)
// 获取父类字段Base的字段的值
bv := v.FieldByIndex([]int{0,0})
fmt.Printf("值为:%v\r\n", bv.Interface())
// 通过名称查看获取字段的类型
sf, b := t.FieldByName("hours")
fmt.Printf("名称:%v; 类型:%v; 是否读取成功:%v", sf.Name ,sf.Type, b)
}
运行结果:
五、函数调用
func (v Value) Call(in []Value) []Value
func (v Value) MethodByName(name string) Value
package main
import (
"fmt"
"reflect"
)
type Func struct {
Name string
Age int
}
// 不改变值
func (f Func)SetName(name string, age int) {
f.Name = name
f.Age = age
}
// 改变值
func (f *Func)TestMethod(name string, age int) {
f.Name = name
f.Age = age
}
/**
ValueOf
*/
func main() {
d := Func{"jadeshu",12}
// 获取实例反射值
v := reflect.ValueOf(d)
// 等同fmt.Println(v.Interface())
fmt.Println("实例原始反射值:", v)
// 获取SetName方法,但是值没有改变
cal := v.MethodByName("SetName")
if cal.IsValid() {
fmt.Println("Func实例反射成功调用SetName函数,没有改变值")
// 调用方法
cal.Call([]reflect.Value{reflect.ValueOf("testFunc"),reflect.ValueOf(20)})
fmt.Println("成功调用函数SetName后的值:", v)
}
//获取失败,因为Func实例没有实现以实例为接收者的TestMethod函数
// 所以实例不能调用TestMethod函数
cal = v.MethodByName("TestMethod")
if cal.IsValid() {
// 调用方法
cal.Call([]reflect.Value{reflect.ValueOf("testFunc"),reflect.ValueOf(20)})
} else {
fmt.Println("实例反射纸获取函数TestMethod失败")
}
fmt.Println("===================================")
// 获取实例指针反射值
v2 := reflect.ValueOf(&d)
fmt.Println("指针实例原始反射值:", v2)
// SetName,该方法是实例接收者,所以值或者指针都可以调用
// 但是不改变值
cal = v2.MethodByName("SetName")
if cal.IsValid() {
fmt.Println("Func指针实例反射成功调用SetName函数,没有改变值")
// 调用方法
cal.Call([]reflect.Value{reflect.ValueOf("testFunc1"),reflect.ValueOf(100)})
fmt.Println(v2)
}
// TestMethod,该方法是指针接收者,所以只能指针去调用
// 改变了值
cal = v2.MethodByName("TestMethod")
if cal.IsValid() {
fmt.Println("Func指针实例反射成功调用TestMethod函数,改变了值")
// 调用方法
cal.Call([]reflect.Value{reflect.ValueOf("testFunc2"),reflect.ValueOf(200)})
fmt.Println(v2)
}
}
六、附件参考:2个重要的类型
- Type
- Value
其中Type是interface类型,Value是struct类型,意识到这一点很重要
Type和Value拥有的同名方法
Type | Value | 备注 |
---|---|---|
Kind | Kind | 返回指定对象的Kind类型 |
MethodByName | MethodByName | 根据方法名找方法 |
Method | Method | 返回第i个方法 |
NumMethod | NumMethod | 返回拥有的方法总数,包括unexported方法 |
Field | Field | 取struct结构的第n个field |
FieldByIndex | FieldByIndex | 嵌套的方式取struct的field,比如v.FieldByIndex(1,2,3)等价于 v.field(1).field(2).field(3) |
FieldByNameFunc | FieldByNameFunc | 返回名称匹配match函数的field |
NumField | NumField | 返回struct所包含的field数量 |
Type独有的方法
方法名 | 备注 |
---|---|
Align | 分配内存时的内存对齐字节数 |
FieldAlign | 作为struct的field时内存对齐字节数 |
Name | type名 string类型 |
PkgPath | 包路径, "encoding/base64", 内置类型返回empty string |
Size | 该类型变量占用字节数 |
String | type的string表示方式 |
Implements | 判断该类型是否实现了某个接口 |
AssignableTo | 判断该类型能否赋值给某个类型 |
ConvertibleTo | 判断该类型能否转换为另外一种类型 |
Comparable | 判断该类型变量是否可以比较 |
ChanDir | 返回channel的方向 recv/send/double |
IsVariadic | 判断函数是否接受可变参数 |
Elem | 取该类型的元素 |
In | 函数第n个入参 |
Out | 函数第n个出参 |
NumIn | 函数的入参数个数 |
NumOut | 函数的出参个数 |
Key | 返回map结构的key类型Type |
Len | 返回array的长度 |
Value独有的方法
方法名 | 备注 |
---|---|
Addr | v的指针,前提时CanAddr()返回true |
Bool | bool类型变量的值 |
Bytes | []bytes类型的值 |
Call | 调用函数 |
CallSlice | 调用具有可变参的函数 |
CanAddr | 判断能否取址 |
CanInterface | 判断Interface方法能否使用 |
CanSet | 判断v的值能否改变 |
Cap | 判断容量 Array/Chan/Slice |
Close | 关闭Chan |
Complex | |
Convert | 返回将v转换位type t的结果 |
Elem | 返回interface包含的实际值 |
Float | |
Index | 索引操作 Array/Slice/String |
Int | |
Interface | 将当前value以interface{}形式返回 |
IsNil | 判断是否为nil,chan, func, interface, map, pointer, or slice value |
IsValid | 是否是可操作的Value,返回false表示为zero Value |
Len | 适用于Array, Chan, Map, Slice, or String |
MapIndex | 对map类型按key取值 |
MapKeys | map类型的所有key的列表 |
OverflowComplex | |
OverflowFloat | 溢出判断 |
OverflowInt | |
OverflowUint | |
Pointer | 返回uintptr 适用于slice |
Recv | chan接收 |
Send | chan发送 |
Set | 将x赋值给v,类型要匹配 |
SetBool | |
SetBytes | |
SetCap | slice调整切片 |
SetMapIndex | map赋值 |
SetUint | |
SetPointer | unsafe.Pointer赋值 |
SetString | |
Slice | return v[i:j] 适用于Array/Slict/String |
String | return value的string表示方法 |
TryRecv | chan非阻塞接收 |
Try Send | chan非阻塞发送 |
Type | 返回value的Type |
UnsafeAddr | 返回指向value的data的指针 |
以上列出的方法及备注只是为了方便理解和记忆,使用时请参考go源码及package说明
Kind类型
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)