目录

一 反射简介

反射是指在程序运行期对程序本身进行访问和修改的能力,即可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind),如果是结构体变量,还可以获取到结构体本身的信息(字段与方法),通过反射,还可以修改变量的值,可以调用关联的方法。

反射常用在框架的开发上,一些常见的案例,如JSON序列化时候tag标签的产生,适配器函数的制作等,都需要用到反射。反射的两个使用常见使用场景:

  • 不知道函数的参数类型:没有约定好参数、传入类型很多,此时类型不能统一表示,需要反射

  • 不知道调用哪个函数:比如根据用户的输入来决定调用特定函数,此时需要依据函数、函数参数进行反射,在运行期间动态执行函数

Go程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树( AST) 对源码进行扫描后获得这些信息。

贴士:

  • C,C++没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息。

  • Java、 C#等语言都支持完整的反射功能。

  • Lua、JavaScript类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。

注意:

  • 在编译期间,无法对反射代码进行一些错误提示。

  • 反射影响性能

二 反射是如何实现的

反射是通过接口的类型信息实现的,即反射建立在类型的基础上:当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息。

Go中反射相关的包是reflect,在该包中,定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

变量包括type、value两个部分(所以 nil != nil ),type包括两部分:

  • static type:在开发时使用的类型,如int、string

  • concrete type:是runtime系统使用的类型

类型能够断言成功,取决于 concrete type ,如果一个reader变量,如果 concrete type 实现了 write 方法,那么它可以被类型断言为writer。

Go中,反射与interface类型相关,其type是 concrete type,只有interface才有反射!每个interface变量都有一个对应的pair,pair中记录了变量的实际值和类型(value, type)。即一个接口类型变量包含2个指针,一个指向对应的 concrete type ,另一个指向实际的值 value。

示例:

var r io.Reader                // 定义了一个接口类型
r, err := os.OpenFile()        // 记录接口的实际类型、实际值

var w io.Writer                // 定义一个接口类型
w = r.(io.Writer)            // 赋值时,接口内部的pair不变,所以 w 和 r 是同一类型

三 Go中反射初识

3.1 reflect包的两个函数

reflect 提供了2个重要函数:

  • ValueOf():获取变量的值,即pair中的 value

  • TypeOf():获取变量的类型,即pair中的 concrete type

    type Person struct {
        Name string
        Age int
    }
    p := Person{ "lisi", 13}

    fmt.Println(reflect.ValueOf(p))            // {lisi 13}  变量的值
    fmt.Println(reflect.ValueOf(p).Type())    // main.Person 变量类型的对象名

    fmt.Println(reflect.TypeOf(p))            //  main.Person    变量类型的对象名

    fmt.Println(reflect.TypeOf(p).Name())    // Person:变量类型对象的类型名
    fmt.Println(reflect.TypeOf(p).Kind())    // struct:变量类型对象的种类名

    fmt.Println(reflect.TypeOf(p).Name() == "Person")    // true
    fmt.Println(reflect.TypeOf(p).Kind() == reflect.Struct)    //true

类型与种类的区别:

  • Type是原生数据类型: int、string、bool、float32 ,以及 type 定义的类型,对应的反射获取方法是 reflect.Type 中 的 Name()

  • Kind是对象归属的品种:Int、Bool、Float32、Chan、String、Struct、Ptr(指针)、Map、Interface、Fune、Array、Slice、Unsafe Pointer等

3.2 静态类型与动态类型

静态类型:变量声明时候赋予的类型

    type MyInt int            // int 是静态类型
    var i *int                // *int 是静态类型

动态类型:运行时给这个变量赋值时,这个值的类型即为动态类型(为nil时没有动态类型)。

    var A interface{}        // 空接口 是静态类型,必须是接口类型才能实现类型动态变化
    A = 10                    // 此时静态类型为 interface{} 动态为int
    A = "hello"                // 此时静态类型为 interface{} 动态为string