go 反射
反射的概念
反射就是程序能够在运行时动态的查看自己的状态,比关切允许修改自
身的行为。
1、GO的反射基础是接口和类型系统,
2、反射的优点:1)、通用性,可对类库和框架代码做极大的简化设计;2)、灵活性,对一些测试工具的开发提供良好的支持。
3、反射的缺点:1)、脆弱,不正确的修改很容易导致程序的崩溃;2)、难懂,反射接口运行时,没有具体的类型系统的约束,接口的抽象及实现细节复杂,导致代码难以理解;3)、性能损失,动态修改状态必然不是直接的地址引用,而是借助运行时构造一个抽象层来间接访问,导致性能损失。
4、用途:尽量在一些库或框架内部代码中使用。
5、Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,Golang的gRPC也是通过反射实现的。
所有类型通用的方法
//返回包含包名的类型名字,对于未命名类型返回的是空
Name() string
//Kind返回该类型的底层基础类型
Kind() Kind
//确定当前类型是否实现了u接口类型
//注意这里的u必须是接口类型的Type
Implements(u Type) bool
//判断当前类型的实例是否能赋值给type为u的类型变量
AssignableTo(u Type) bool
//判断当前类型的实例是否能强制类型转换为u类型变量
ConvertibleTo(u Type) bool
//判断当前类型是否支持比较(等于或不等于)
//支持等于的类型可以作为map的key
Comparable() bool
//返回一个类型的方法的个数
NumMethod() int
//通过索引值访问方法,索引值必须属于[0,NumMethod()],否则引发panic
Method(int) Method
//通过方法名获取Method
MethodByName(string) (Method,bool)
//返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
PkgPath() string
//返回存放该类型的实例需要多大的字节空间
size() uintptr
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFunc() {
fmt.Println("Allen.Wu ReflectCallFunc")
}
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)
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
说明
1、要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来获取到reflect.Value,得到“反射类型对象”后才能做下一步处理
2、reflect.Value.MethodByName这.MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字。
3、[]reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
4、reflect.Value的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果reflect.Value'Kind不是一个方法,那么将直接panic。
5、本来可以用u.ReflectCallFuncXXX直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是MethodByName,然后通过反射调用methodValue.Call
使用反射的实例:inject包 依赖注入:https://www.zddhub.com/memo/2015/07/05/go-dependency-inject.html
反射三定律:
(1)反射可以从接口值得到反射对象;
(2)反射可以从反射对象获取接口值;
(3)若要修改一个反射对象,则其值必须为可修改。
1、Golang的gRPC也是通过反射实现的。
2、控制反转,依赖注入
类型断言在GO中指的就是判断接口型变量运行时的实际类型