Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。
以下是需要反射的 2 个常见场景:
- 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了
- 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
我们以前学习过interface,当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。而Go语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。
变量包括(type, value)两部分,而type 包括 static type和concrete type。static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型。
Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了,而运行时才知道变量类型的叫做动态类型
示例:
type MyInt int
var i int
var j MyInt
我们都知道i和j的底层类型都是int,但是他们是不同的静态类型,i和j不能比较大小等数值比较的操作,因为j 的静态类型是 MyInt,不然会编译报错mismatched types int and MyInt
var A interface{} // 静态类型interface{}
A = 10 // 静态类型为interface{} 动态为int
A = "String" // 静态类型为interface{} 动态为string
var M *int
A = M // A的值可以改变
在Go的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value, type)
,value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型(concrete type),另外一个指针指向实际的值(value)。
在 Go 语言中,
reflect
实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。
接下来,我们来了解reflect 包中的几种类型和方法:
reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。
reflect.TypeOf() 和 reflect.ValueOf() 两个函数可以分别返回 reflect.Type 和 reflect.Value。
//ValueOf用来获取输入参数接口中的数据的值,如果接口为空(nil)则返回0
func ValueOf(i interface{}) Value {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空(nil)则返回nil
func TypeOf(i interface{}) Type {...}
示例:
package main
import (
"fmt"
"reflect"
)
type myInt int
type st struct {
name string
number int
}
type st2 struct {
name string
number int
}
func check(i interface{}) {
ty := reflect.TypeOf(i)
value := reflect.ValueOf(i)
fmt.Println("Type ", ty)
fmt.Println("Value ", value)
}
func main() {
var v1 myInt
v1 = 1
v2 := st{name: "st1", number: 1}
v3 := st2{name: "st2", number: 2}
fmt.Println("====v1======")
check(v1)
fmt.Println("====v2======")
check(v2)
fmt.Println("====v3======")
check(v3)
}
输出:
====v1======
Type main.myInt
Value 1
====v2======
Type main.st
Value {st1 1}
====v3======
Type main.st2
Value {st2 2}
reflect.Type 变量 和 reflect.Value 变量都可以通过 Kind() 方法返回对应的接口变量的基础类型。
reflect/type.go
type Type interface {
...
Kind() Kind
...
}
reflect/value.go
func (v Value) Kind() Kind{
...
}
我们可以阅读源码reflect/type.go
看到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
)
现在我们把上面示例中的check方法增加多两句输出,也可以看到Kind方法实际返回的类型:
...
func check(i interface{}) {
ty := reflect.TypeOf(i)
value := reflect.ValueOf(i)
fmt.Println("Type ", ty)
fmt.Println("Value ", value)
fmt.Println("Type.Kind() ", ty.Kind())
fmt.Println("Value.Kind() ", value.Kind())
}
...
输出:
====v1======
Type main.myInt
Value 1
Type.Kind() int
Value.Kind() int
====v2======
Type main.st
Value {st1 1}
Type.Kind() struct
Value.Kind() struct
====v3======
Type main.st2
Value {st2 2}
Type.Kind() struct
Value.Kind() struct
NumField() 方法返回结构体中字段的数量,而 Field(i int) 方法结构体中第i个字段的reflect.Value。
func (v Value) Field(i int) Value {
...
}
...
func (v Value) NumField() int {
...
}
示例:
package main
import (
"fmt"
"reflect"
)
type student struct {
id int
name string
age int
}
func main() {
stu := student{id: 1001, name: "小黄", age: 16}
value := reflect.ValueOf(stu)
fmt.Println("字段数:", value.NumField())
for i:=0;i<value.NumField();i++{
fmt.Printf("第 %d 个字段:%v \n", i+1, value.Field(i))
}
}
输出:
字段数: 3
第 1 个字段:1001
第 2 个字段:小黄
第 3 个字段:16
func (v Value) Int() int64 {
...
}
...
func (v Value) String() string {
...
}
示例:
package main
import (
"fmt"
"reflect"
)
func main() {
a := 111
b := "string"
aValue := reflect.ValueOf(a).Int()
bValue := reflect.ValueOf(b).String()
fmt.Println("Int():", aValue)
fmt.Println("String():", bValue)
}
输出:
Int(): 111
String(): string
几点不太建议使用反射的理由:
- 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
- Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接
panic,可能会造成严重的后果。- 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
Go官方提到的反射三大定律:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
翻译:
- 反射将接口变量转换成反射对象 Type 和 Value
- 通过反射可以将反射对象 Value 还原成原先的接口变量
- 若要修改反射对象,该值必须是可设置的
反射将接口变量转换成反射对象 Type 和 Value
这个比较好理解,其实就是通过reflect.TypeOf
和reflect.ValueOf
函数得到接口变量中的具体类型和值。
通过反射可以将反射对象 Value 还原成原先的接口变量
第二条与第一条定律相反,其实就是将reflect.ValueOf
方法得到的reflect.Value
值通过Interface()
方法转回interface 变量。
若要修改反射对象,该值必须是可设置的
这个相对来说比较难理解,我们可以通过一个经典的例子来理解:
package main
import "reflect"
func main() {
var x int
x = 1
v := reflect.ValueOf(x)
v.SetInt(2) //panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
}
其panic的原因是因为反射变量 v是不可以设置的。为什么v不可以设置,因为传入的参数在ValueOf函数内部只是一个拷贝,是值传递。所以v是不能代表x本身的,对v的修改只是代表了对x的拷贝值进行了修改。
那么如果我们想反射变量是可设置的,应该怎么做?
那就像Go中的引用传递一样,向函数中传入指针变量
...
func main() {
var x int
x = 1
v := reflect.ValueOf(&x)
fmt.Println(v.CanSet()) //false
}
为什么v还是不可设置?是因为v.Elem() 才真正代表 x
...
func main() {
var x int
x = 1
v := reflect.ValueOf(&x)
fmt.Println(v.Elem().CanSet()) //true
}
注意:一个可取地址的 reflect.Value 变量会记录一个结构体成员是否是未导出成员,如果是的话则拒绝修改操作。
示例:
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
isAdult bool
}
func main() {
p := Person{Name: "小黄", isAdult: false}
v := reflect.ValueOf(&p)
f := v.Elem().FieldByName("Name")
f.SetString("老黄")
fmt.Println(f.String())
f = v.Elem().FieldByName("isAdult")
f.SetBool(true) //panic: reflect: reflect.flag.mustBeAssignable using value obtained using unexported field
}