什么是反射
官方关于反射定义:
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
(在计算机领域,反射是一种让程序——主要是通过类型——理解其自身结构的一种能力。它是元编程的组成之一,同时它也是一大引人困惑的难题。)
维基百科关于反射的定义:
在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
《Go语言圣经》关于反射的定义:
Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
interface{}
根据以上定义,可以得出:
反射是指在程序运行时对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
变量的内在机制
typevaluenil != niltypestatic typeconcrete typestatic typeintstringconcrete typeruntimeconcrete typestatic typereaderconcrete typewritewriter
Go是静态类型语言。每个变量都拥有一个静态类型,这意味着每个变量的类型在编译时都是确定的:int,float32, *AutoType, []byte, chan []int 诸如此类。
在反射的概念中, 编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型。
- 静态类型: 静态类型就是变量声明时的赋予的类型:
type MyInt int // int 就是静态类型
type A struct{
Name string // string就是静态
}
var i *int // *int就是静态类型
- 动态类型:运行时给这个变量赋值时,这个值的类型(如果值为nil的时候没有动态类型)。一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量是接口类型)。
var A interface{} // 静态类型interface{}
A = 10 // 静态类型为interface{} 动态为int
A = "String" // 静态类型为interface{} 动态为string
var M *int
A = M // A的值可以改变
interfaceinterface
interfacepairpair
(value, type)
interface{}
*os.Filer
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
var r io.Reader
r = tty
rpair(tty, *os.File)pairrw
var w io.Writer
w = r.(io.Writer)
wpairrpair(tty, *os.File)wpair
interface及其pair的存在,是Golang中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。
所以要理解两个基本概念 Type 和 Value,它们也是 Go语言包中 reflect 空间里最重要的两个类型。
reflect
reflect
interface
一个具体类型具体类型的值reflectreflect.Typereflect.Valuereflectreflect.TypeOfreflect.ValueOfValueType
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero
// 翻译一下:ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
func ValueOf(i interface{}) Value {...}
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
// 翻译一下:TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
func TypeOf(i interface{}) Type {...}
TypeOf
Type
reflect.Value
和 reflect.Value 不同,reflect.Type 是一个接口,而不是一个结构体,所以也只能使用它的方法。
以下 reflect.Type 接口常用的方法。从列表来看,大部分都和 reflect.Value 的方法功能相同。
type Type interface {
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
//以下这些方法和Value结构体的功能相同
Kind() Kind
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
}
其中几个特有的方法如下:
- Implements 方法用于判断是否实现了接口 u;
- AssignableTo 方法用于判断是否可以赋值给类型 u,其实就是是否可以使用 =,即赋值运算符;
- ConvertibleTo 方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;
- Comparable 方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。
reflect.Typereflect.TypeOf()
func main() {
//反射操作:通过反射,可以获取一个接口类型变量的 类型
var x float64 =3.4
fmt.Println("type:",reflect.TypeOf(x)) //type: float64
}
type nametype kind
类型(Type)种类(Kind)type种类(Kind)种类(Kind)
type myInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
func main() {
var a *float32 // 指针
var b myInt // 自定义类型
var c rune // 类型别名
reflectType(a) // type: kind:ptr
reflectType(b) // type:myInt kind:int64
reflectType(c) // type:int32 kind:int32
type person struct {
name string
age int
}
type book struct{ title string }
var d = person{
name: "张三",
age: 25,
}
var e = book{title: "《Go语言圣经》"}
reflectType(d) // type:person kind:struct
reflectType(e) // type:book kind:struct
}
.Name()空
reflectKind
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
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 // 底层指针
)
reflect.Typepersonfmt.Stringerio.Writer
func main() {
p:=person{Name: "张三",Age: 20}
pt:=reflect.TypeOf(p)
stringerType:=reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
writerType:=reflect.TypeOf((*io.Writer)(nil)).Elem()
fmt.Println("是否实现了fmt.Stringer:",pt.Implements(stringerType))
fmt.Println("是否实现了io.Writer:",pt.Implements(writerType))
}
尽可能通过类型断言的方式判断是否实现了某接口,而不是通过反射。
通过 Implements 方法来判断是否实现了 fmt.Stringer 和 io.Writer 接口,运行结果:
是否实现了fmt.Stringer: false
是否实现了io.Writer: false
ValueOf
reflect.ValueOf()reflect.Valuereflect.Value
reflect.Valuestruct
// Value is the reflection interface to a Go value.
//
// Not all methods apply to all kinds of values. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of value before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run time panic.
//
// The zero Value represents no value.
// Its IsValid method returns false, its Kind method returns Invalid,
// its String method returns "<invalid Value>", and all other methods panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
//
// A Value can be used concurrently by multiple goroutines provided that
// the underlying Go value can be used concurrently for the equivalent
// direct operations.
//
// To compare two Values, compare the results of the Interface method.
// Using == on two Values does not compare the underlying values
// they represent.
type Value struct {
// typ holds the type of the value represented by a Value.
typ *rtype
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer
// flag holds metadata about the value.
// The lowest bits are flag bits:
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
// The next five bits give the Kind of the value.
// This repeats typ.Kind() except for method values.
// The remaining 23+ bits give a method number for method values.
// If flag.kind() != Func, code can assume that flagMethod is unset.
// If ifaceIndir(typ), code can assume that flagIndir is set.
flag
// A method value represents a curried method invocation
// like r.Read for some receiver r. The typ+val+flag bits describe
// the receiver r, but the flag's Kind bits say Func (methods are
// functions), and the top bits of the flag give the method number
// in r's type's method table.
}
reflect.Valuereflect.Value
Interface() interface {}Int() int64Uint() uint64Float() float64Bool() boolBytes() []bytesString() stringCanSet() boolElem() TypeKind() Kind
通过反射获取值
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 100
reflectValue(a) // type is float32, value is 3.140000
reflectValue(b) // type is int64, value is 100
// 将int类型的原始值转换为reflect.Value类型
c := reflect.ValueOf(10)
fmt.Printf("type c :%T\n", c) // type c :reflect.Value
}
通过反射设置变量的值
Elem()
func main() {
var a int64 = 10
v := reflect.ValueOf(a)
if v.Kind() == reflect.Int64 {
v.SetInt(20) //panic: reflect: reflect.Value.SetInt using unaddressable value
}
}
func main() {
var a int64 = 10
v := reflect.ValueOf(&a) //反射获取指针的地址
// 反射中使用 Elem()方法获取指针对应的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(20)
}
fmt.Println(a) //20
}
isNil()isValid()
isNil()
func (v Value) IsNil() bool
IsNil()
isValid()
func (v Value) IsValid() bool
IsValid()
IsNil()IsValid()
func main() {
// *int类型空指针
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil()) //var a *int IsNil: true
// nil值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid()) //nil IsValid: false
// 实例化一个匿名结构体
b := struct{}{}
// 尝试从结构体中查找"abc"字段
fmt.Println("b结构体是否存在成员abc:", reflect.ValueOf(b).FieldByName("abc").IsValid()) //b结构体是否存在成员abc: false
// 尝试从结构体中查找"abc"方法
fmt.Println("b结构体是否存在方法abc:", reflect.ValueOf(b).MethodByName("abc").IsValid()) //b结构体是否存在方法abc: false
// map
c := map[string]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("map中是否存在键张三:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("张三")).IsValid()) //map中是否存在键张三: false
}
结构体反射
与结构体相关的方法
reflect.TypeOf()reflect.TypeNumField()Field()
reflect.Type
方法 | 说明 |
---|---|
Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
NumField() int | 返回结构体成员字段数量。 |
FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
NumMethod() int | 返回该类型的方法集中方法的数目 |
Method(int) Method | 返回该类型方法集中的第i个方法 |
MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
StructField
StructField
StructField
type StructField struct {
// 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string // Name是字段的名字。
PkgPath string // PkgPath是非导出字段的包路径,对导出字段该字段为""。
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引切片
Anonymous bool // 是否匿名字段
}
结构体反射示例
当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息。
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
stu := student{
Name: "张三",
Score: 90,
}
t := reflect.TypeOf(stu)
fmt.Println(t.Name(), t.Kind()) // student struct
// 通过for循环遍历结构体的所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}
// 通过字段名获取指定结构体字段信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
运行结果:
student struct
name:Name index:[0] type:string json tag:name
name:Score index:[1] type:int json tag:score
name:Score index:[1] type:int json tag:score
printMethod(s interface{})
func (s student) Study() string {
msg := "学习"
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "睡觉"
fmt.Println(msg)
return msg
}
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println(t.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:%s\n", t.Method(i).Name)
fmt.Printf("method:%s\n", methodType)
// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
args := []reflect.Value{}
v.Method(i).Call(args)
}
}
运行结果:
2
method name:Sleep
method:func() string
睡觉
method name:Study
method:func() string
学习
反射的规则
根据上面对反射的大致介绍,对反射有了一定的了解,其实反射的操作步骤非常的简单,就是通过实例对象获取反射对象(Value、Type),然后操作相应的方法
实例、Value、Type 三者之间的转换关系:
Value
通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数:
func ValueOf(i interface {}) Value
Type
通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数:
func TypeOf(i interface{}) Type
TypeValue
Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflect 包提供了两种方法,示例如下:
//New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型
func New(typ Type) Value
//Zero 返回的是一个 typ 类型的零佳,注意返回的 Value 不能寻址,位不可改变
func Zero(typ Type) Value
如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的:
func NewAt(typ Type, p unsafe.Pointer) Value
ValueType
从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针:
func (v Value) Type() Type
Value
Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转换:
//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
//可以使用接口类型查询去还原为具体的类型
func (v Value) Interface() (i interface{})
//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64
......
Value
从一个指针类型的 Value 获得值类型 Value 有两种方法:
//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如采 v 类型是指针,则返回指针值的 Value,否则引起 panic
func (v Value) Elem() Value
//如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value
Type
指针类型 Type 到值类型 Type:
//t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic
//Elem 返回的是其内部元素的 Type
func (t *rtype) Elem() Type
值类型 Type 到指针类型 Type:
//PtrTo 返回的是指向 t 的指针型 Type
func PtrTo(t Type) Type
Value
Value 值的修改涉及如下两个方法:
//通过 CanSet 判断是否能修改
func (v Value ) CanSet() bool
//通过 Set 进行修改
func (v Value ) Set(x Value)
实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法 reflect.ValueOf() 传进去的是一个值类型变量, 则获得的 Value 实际上是原对象的一个副本,这个 Value 是无论如何也不能被修改的。
反射是计算机语言中程序检视其自身结构的一种方法,它属于元编程的一种形式。反射灵活、强大,但也存在不安全。它可以绕过编译器的很多静态检查,如果过多使用便会造成混乱。为了帮助开发者更好地理解反射,Go 语言的作者在博客上总结了反射的三大定律。
1.Reflection goes from interface value to reflection object.
2.Reflection goes from reflection object to interface value.
3.To modify a reflection object, the value must be settable.
- 任何接口值 interface{} 都可以反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 获得。
- 反射对象也可以还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 结构体的 Interface 方法获得。
- 要修改反射的对象,该值必须可设置,也就是可寻址。
任何类型的变量都可以转换为空接口 intferface{},所以第 1 条定律中函数 reflect.ValueOf 和 reflect.TypeOf 的参数就是 interface{},表示可以把任何类型的变量转换为反射对象。在第 2 条定律中,reflect.Value 结构体的 Interface 方法返回的值也是 interface{},表示可以把反射对象还原为对应的类型变量。
反射的使用
从relfect.Value中获取接口interface的信息
当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,可能是已知原有类型,也有可能是未知原有类型:
已知原有类型
已知类型后转换为其对应的类型的做法如下,直接通过Interface方法然后强制转换,如下:
realValue := value.Interface().(已知的类型)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
// Golang 对类型要求非常严格,类型一定要完全符合
// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
运行结果:
0xc000098000
1.2345
说明
- 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
- 转换的时候,要区分是指针还是值
- 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
未知原有类型
很多情况下,可能并不知道其具体类型,那么就需要进行遍历探测其Filed来得知:
type Person struct {
Name string
Age int
Sex string
}
func (p Person) Say(msg string) {
fmt.Println("hello,",msg)
}
func (p Person) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex)
}
func main() {
p1 := Person{"张三",25,"男"}
DoFiledAndMethod(p1)
}
// 通过接口来获取任意参数
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input) //先获取input的类型
fmt.Println("get Type is :", getType.Name()) // Person
fmt.Println("get Kind is : ", getType.Kind()) // struct
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue) //{张三 25 男}
// 获取方法字段
// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
// 2. 再通过reflect.Type的Field获取其Field
// 3. 最后通过Field的Interface()得到对应的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface() //获取第i个值
fmt.Printf("字段名称:%s, 字段类型:%s, 字段数值:%v \n", field.Name, field.Type, value)
}
// 通过反射,操作方法
// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
// 2. 再公国reflect.Type的Method获取其Method
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法名称:%s, 方法类型:%v \n", method.Name, method.Type)
}
}
运行结果:
get Type is : Person
get Kind is : struct
get all Fields is: {张三 25 男}
字段名称:Name, 字段类型:string, 字段数值:张三
字段名称:Age, 字段类型:int, 字段数值:25
字段名称:Sex, 字段类型:string, 字段数值:男
方法名称:PrintInfo, 方法类型:func(main.Person)
方法名称:Say, 方法类型:func(main.Person, string)
说明
interface
- 先获取interface的reflect.Type,然后通过NumField进行遍历
- 再通过reflect.Type的Field获取其Field
- 最后通过Field的Interface()得到对应的value
interface
- 先获取interface的reflect.Type,然后通过NumMethod进行遍历
- 再分别通过reflect.Type的Method获取对应的真实的方法(函数)
- 最后对结果取其Name和Type得知具体的方法名
- 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
- struct 或者 struct 的嵌套都是一样的判断处理方式
如果是struct的话,可以使用Elem()
tag := t.Elem().Field(0).Tag //获取定义在struct里面的Tag属性
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
reflect.Value
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是可寻址的。
这里需要一个方法:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value
解释起来就是:Elem返回接口v包含的值或指针v指向的值。如果v的类型不是interface或ptr,它会恐慌。如果v为零,则返回零值。
func main() {
var num float64 = 1.2345
fmt.Println("old value of pointer:", num)
// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()
fmt.Println("type of pointer:", newValue.Type())
fmt.Println("settability of pointer:", newValue.CanSet())
// 重新赋值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num)
////////////////////
// 如果reflect.ValueOf的参数不是指针,会如何?
//pointer = reflect.ValueOf(num)
//newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}
运行结果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77
说明
- 需要传入的参数是* float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针。
- 如果传入的参数不是指针,而是变量,那么
- 通过Elem获取原始值对应的对象则直接panic
- 通过CanSet方法查询是否可以设置返回false
- newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
- reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
- 也就是说如果要修改反射类型对象,其值必须是可寻址的【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】
- struct 或者 struct 的嵌套都是一样的判断处理方式
reflect.Value
在项目应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法的调用。比如要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,关键点在于用户的自定义方法是未可知的,因此可以通过reflect来搞定。
Call()
// Call calls the function v with the input arguments in.
// For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
// Call panics if v's Kind is not Func.
// It returns the output results as Values.
// As in Go, each input argument must be assignable to the
// type of the function's corresponding input parameter.
// If v is a variadic function, Call creates the variadic slice parameter
// itself, copying in the corresponding values.
func (v Value) Call(in []Value) []Value
通过反射,调用方法。
type Person struct {
Name string
Age int
Sex string
}
func (p Person) Say(msg string) {
fmt.Println("hello,",msg)
}
func (p Person) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex)
}
func (p Person) Test(i,j int,s string){
fmt.Println(i,j,s)
}
// 如何通过反射来进行方法的调用?
// 本来可以用结构体对象.方法名称()直接调用的,
// 但是如果要通过反射,
// 那么首先要将方法注册,也就是MethodByName,然后通过反射调动mv.Call
func main() {
p2 := Person{"张三",25,"男"}
// 1. 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,
// 得到“反射类型对象”后才能做下一步处理
getValue := reflect.ValueOf(p2)
// 2.一定要指定参数为正确的方法名
// 先看看没有参数的调用方法
methodValue1 := getValue.MethodByName("PrintInfo")
fmt.Printf("Kind : %s, Type : %s\n",methodValue1.Kind(),methodValue1.Type())
methodValue1.Call(nil) //没有参数,直接写nil
args1 := make([]reflect.Value, 0) //或者创建一个空的切片也可以
methodValue1.Call(args1)
// 有参数的方法调用
methodValue2 := getValue.MethodByName("Say")
fmt.Printf("Kind : %s, Type : %s\n",methodValue2.Kind(),methodValue2.Type())
args2 := []reflect.Value{reflect.ValueOf("反射机制")}
methodValue2.Call(args2)
methodValue3 := getValue.MethodByName("Test")
fmt.Printf("Kind : %s, Type : %s\n",methodValue3.Kind(),methodValue3.Type())
args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200),reflect.ValueOf("Hello")}
methodValue3.Call(args3)
}
运行结果:
Kind : func, Type : func()
姓名:张三,年龄:25,性别:男
姓名:张三,年龄:25,性别:男
Kind : func, Type : func(string)
hello, 反射机制
Kind : func, Type : func(int, int, string)
100 200 Hello
通过反射,调用函数。
Fun()f1 := Funf1f1()Fun()
ValueOf()KindfuncCall()
func main() {
//函数的反射
f1 := fun1
value := reflect.ValueOf(f1)
fmt.Printf("Kind : %s , Type : %s\n",value.Kind(),value.Type()) //Kind : func , Type : func()
value2 := reflect.ValueOf(fun2)
fmt.Printf("Kind : %s , Type : %s\n",value2.Kind(),value2.Type()) //Kind : func , Type : func(int, string)
//通过反射调用函数
value.Call(nil)
value2.Call([]reflect.Value{reflect.ValueOf(100),reflect.ValueOf("hello")})
}
func fun1(){
fmt.Println("函数fun1(),无参。。")
}
func fun2(i int, s string){
fmt.Println("函数fun2(),有参数。。",i,s)
}
说明
- 要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
- reflect.Value.MethodByName这个MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。
- []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
- reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果reflect.Value.Kind不是一个方法,那么将直接panic。
- 本来可以用对象访问方法直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用methodValue.Call
反射是把双刃剑
反射是一个强大并富有表现力的工具,能写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
- 大量使用反射的代码通常难以理解,代码可读性差。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。处于运行效率关键位置的代码,请避免使用反射。