go 反射

2019-05-19  本文已影响0人  乘风破浪_6a9f

反射的概念

反射就是程序能够在运行时动态的查看自己的状态,比关切允许修改自
身的行为。
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中指的就是判断接口型变量运行时的实际类型

上一篇下一篇

猜你喜欢

热点阅读