在计算机科学中,反射是指计算机程序在运行时(Run time)能够访问、检测和修改它自己状态或行为的一种能力。用比喻来讲,反射就是程序在运行的时候可以“观察”而且修改本身的行为。bash
Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,可是在编译时并不知道这些变量的具体类型,这称为反射机制(refletion)。函数
Go 语言官方自带的 reflect 包就是实现反射相关的,reflect 包定义了各类类型,实现了反射的各类函数,经过它们能够在运行时检测类型的信息、改变类型的值。工具
须要反射的 2 个常见场景:布局
Go 语言的 fmt.Printf 函数中的格式化逻辑就是使用反射处理相似以上存在的问题来实现的。测试
下面尝试实现一个相似 fmt.Printf 功能的函数,为了简单起见,函数只接收一个参数,而后返回和 fmt.Sprint 相似的格式化后的字符串,函数名也叫 Sprint。ui
首先用 switch 类型分支来测试输入参数是否实现了 String 方法,若是是就调用该方法。而后继续增长类型测试分支,检查这个值的动态类型是不是 string、int、bool 等基础类型,并在每种状况下执行相应的格式化操做。url
func Sprint(x interface{}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
// ...similar cases for int16, uint32, and so on...
case bool:
if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
复制代码
以上函数虽然实现了部分类型的格式化输出,可是如何处理其它相似 []float6四、map[string][]string 等类型呢?固然能够添加更多的测试分支,可是这些组合类型的数目基本是无穷的。还有如何处理相似url.Values这样的具名类型呢?即便类型分支能够识别出底层的基础类型是 map[string][]string,可是它并不匹配 url.Values 类型,由于它们是两种不一样的类型,并且 switch 类型分支也不可能包含每一个相似 url.Values 的类型,这会致使对这些库的依赖。spa
没有办法来检查未知类型的表示方式,被卡住了。这就是为什么须要反射的缘由。设计
interface 是 Go 语言实现抽象的一个很是强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是经过接口的类型信息实现的,反射创建在类型的基础上。指针
Go 语言关于类型设计的一些原则:
变量包括 type, value 这两部分。其中 type 包括 static type 和 concrete type,static type 是在编写程序时看见的类型(即变量声明时赋予的类型,如int、string),concrete type 是 runtime 系统时看见的类型(即运行时给这个变量赋值后,该变量的类型)。
类型断言可否成功,取决于变量的 concrete type,而不是 static type。因此,一个 reader 变量若是它的 concrete type 也实现了 write 方法,它能够被类型断言为 writer。
反射创建在类型之上,Go 语言声明变量时指定的 type 是 static type,在建立变量的时候就已经肯定。反射主要与 Go 语言的 interface 类型相关(它的 type 是 concrete type ),只有 interface 类型才有反射一说。
Go 语言中,每一个 interface 变量都有一个对应 pair,pair 中记录了实际变量的值和类型:
(value, type)
复制代码
以上,value 是实际变量值,type 是实际变量的类型。一个 interface{} 类型的变量包含了2个指针,一个指针指向值的类型【对应 concrete type】,另一个指针指向实际的值【对应 value】。
Go 语言的反射功能由 reflect 包提供,它实现了运行时反射,使用它能识别 interface{} 变量的底层具体类型和具体值。
1. reflect.Type 和 reflect.Value
reflect 包定义了两个重要的类型:Type 和 Value。reflect.Type 表示 interface{} 的具体类型,而 reflect.Value 表示它的具体值。reflect.TypeOf() 和 reflect.ValueOf() 两个函数能够分别返回 reflect.Type 和 reflect.Value。
- TypeOf 用来动态获取输入参数接口中的值的类型,若是接口为空则返回nil
- ValueOf用来获取输入参数接口中的数据的值,若是接口为空则返回0
即,reflect.TypeOf() 是获取 pair 中的 type,reflect.ValueOf() 获取 pair 中的value,示例以下:
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("type: ", reflect.TypeOf(num))
fmt.Println("value: ", reflect.ValueOf(num))
}
复制代码
运行结果:
type: float64
value: 1.2345
复制代码
2. relfect.Kind
reflect 包中还有一个重要的类型:Kind。 在反射包中,Kind 和 Type 的类型可能看起来很类似,但在下面程序中,能够很清楚地看出它们的不一样之处。
package main
import (
"fmt"
"reflect"
)
type order struct {
ordId int
customerId int
}
func createQuery(q interface{}) {
t := reflect.TypeOf(q)
k := t.Kind()
fmt.Println("Type ", t)
fmt.Println("Kind ", k)
}
func main() {
o := order{
ordId: 456,
customerId: 56,
}
createQuery(o)
}
复制代码
输出:
Type main.order
Kind struct
复制代码
由上可知:Type 表示 interface{} 的实际类型(在这里是 main.Order),而 Kind 表示该类型的特定类别(在这里是 struct)。
当执行 reflect.ValueOf(interface) 以后,就获得了一个类型为 “relfect.Value” 变量,能够经过它自己的 Interface() 方法得到接口变量的真实内容,而后能够经过类型判断进行转换,转换为原有真实类型。
realValue := value.Interface().(已知的类型)
复制代码
示例以下:
package main
import (
"fmt"
"reflect"
)
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)
}
复制代码
运行结果:
0xc42000e238
1.2345
复制代码
说明:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func main() {
user := User{1, "Allen.Wu", 25}
DoFiledAndMethod(user)
}
// 经过接口来获取任意参数,而后一一揭晓
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input)
fmt.Println("get Type is :", getType.Name())
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue)
// 获取方法字段
// 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()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 获取方法
// 1. 先获取interface的reflect.Type,而后经过.NumMethod进行遍历
for i := 0; i < getType.NumMethod(); i++ {
m := getType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
复制代码
运行结果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)
复制代码
说明
经过运行结果能够得知获取未知类型的 interface 的具体变量及其类型的步骤为:
经过运行结果能够得知获取未知类型的interface的所属方法(函数)的步骤为:
reflect.Value 是经过 reflect.ValueOf(x) 得到的,只有当 x 是指针的时候,才能够经过 reflec.Value 修改实际变量 x 的值,即:要修改反射类型的对象就必定要保证其值是 “addressable” 的。 示例以下:
package main
import (
"fmt"
"reflect"
)
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
复制代码
说明
示例以下:
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFuncHasArgs(name string, age int) {
fmt.Println("ReflectCallFuncHasArgs name: ", name, ", age:", age, "and origal User.Name:", u.Name)
}
func (u User) ReflectCallFuncNoArgs() {
fmt.Println("ReflectCallFuncNoArgs")
}
// 如何经过反射来进行方法的调用?
// 原本能够用u.ReflectCallFuncXXX直接调用的,可是若是要经过反射,那么首先要将方法注册,也就是MethodByName,而后经过反射调动mv.Call
func main() {
user := User{1, "Allen.Wu", 25}
// 1. 要经过反射来调用起对应的方法,必需要先经过reflect.ValueOf(interface)来获取到reflect.Value,获得“反射类型对象”后才能作下一步处理
getValue := reflect.ValueOf(user)
// 必定要指定参数为正确的方法名
// 2. 先看看带有参数的调用方法
methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
methodValue.Call(args)
// 必定要指定参数为正确的方法名
// 3. 再看看无参数的调用方法
methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
args = make([]reflect.Value, 0)
methodValue.Call(args)
}
复制代码
运行结果:
ReflectCallFuncHasArgs name: wudebao, age: 30 and origal User.Name: Allen.Wu
ReflectCallFuncNoArgs
复制代码
说明
要经过反射来调用起对应的方法,必需要先经过 reflect.ValueOf(interface) 来获取到 reflect.Value,获得“反射类型对象”后才能作下一步处理
reflect.Value.MethodByName 这 .MethodByName,须要指定准确真实的方法名字,若是错误将直接 panic,MethodByName 返回一个函数值对应的 reflect.Value 方法的名字。
[]reflect.Value,这个是最终须要调用的方法的参数,能够没有或者一个或者多个,根据实际参数来定。
reflect.Value 的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,若是 reflect.Value'Kind 不是一个方法,那么将直接 panic。
原本能够用 u.ReflectCallFuncXXX 直接调用的,可是若是要经过反射,那么首先要将方法注册,也就是 MethodByName,而后经过反射调用 methodValue.Call