Go reflect

reflection

  • 反射(reflection)是程序在运行时通过检查其定义的变量和值获得对应的真实类型。

在计算机科学领域,反射是指一类应用能够自描述和自控制。此类应用采用某种机制来实现自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

  • 反射机制就是在运行时动态的调用对象的方法和属性

每种编程语言的反射模型都不同,有些语言并不支持反射。支持反射的语言可以在程序编译期将变量的反射信息,比如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样可在程序运行期获取类型的反射信息,并有能力改变它们。

  • 反射是指在程序运行期对程序本身进行访问和修改的能力

程序在编译时变量会被转换为内存地址,而变量名不会被编译器写入到可执行部分,因此在运行程序时程序是无法获取自身信息的。

reflect

Golang提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但在编译时并不知道这些变量的具体类型,这种机制称为反射。

反射可将类型本身作为第一类型的值来处理

  • Golang程序在运行期可使用reflect包访问程序的反射信息

Golang提供了reflect包用来实现反射,通过refelct包能够完成对一个interface{}空接口变量的具体类型和值进行获取。

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

interface

  • Golang关于类型设计的原则

变量包括(value, type)两部分,type包括static typeconcrete type,简单来说static type是在编码时可见的类型,concrete typeruntime系统可见的类型。

类型断言能够成功取决于变量的concrete type而非static type,比如一个变量的concrete type如果实现了write方法则可被类型断言为writer

  • 反射是建立在类型之上的

Golang指定类型的变量是static的,因此在创建变量时就已经确定。反射主要与Golang的interface类型相关,因为interface的类型是concrete的,只有interface类型才有反射一说。


  • interfacepair的存在是Golang中实现反射的前提

在Golang的实现中每个interface变量都拥有一对pair(value, type)pair中记录了实际变量的值和类型(value, type)value是实际变量的值type是实际变量的类型

一个interface{}类型的变量包含两个指针:
一个指针下指向实际的值,即对应的value
一个指针指向值的类型,对应concrete type

例如:创建类型为*os.File的变量

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

将变量赋值给一个接口变量

var r io.Reader
r = tty

赋值后接口变量的pair中记录的信息为(tty, *os.File),这个pair在接口变量连续赋值过程中是不会改变的。

比如将接口变量赋值给另一个接口变量

var w io.Writer
w = r.(io.Writer)

此时接口变量w的pair和r的pair相同,都是(tty, *os.File)。即使w是空接口类型,pair也是不变的。

定律

反射三大定律

  1. 反射可以将【接口类型变量】转换为【反射类型对象】
  • 反射类型对象指的是reflect.Typereflect.Value
  • 反射提供了一种允许程序在运行时检查接口变量内部存储的pair(value, type)对的机制
  • reflect包中的ValueType类型时访问接口内部的数据成为可能
  • reflect包为ValueType类型分别提供了reflect.ValueOf()reflect.TypeOf()方法来进行读取
  1. 反射可以将【反射类型对象】转换为【接口类型变量】
  • 和物理学中的反射类似,Golang中的反射也能够创造自己反面类型的对象。
  1. 若要修改【反射类型对象】则其值必须可写
  • 可通过value.CanSet()方法来检查一个reflect.Value类型的变量是否具有“可写性”
  • 可写性类似于寻址能力,但更为严格。它是反射类型变量的一种属性,会赋予该变量修改底层存储数据的能力。
  • 可写性最终是由一个反射对象是否存储原始值而决定的

结构体

  • 使用反射获取结构体中的字段信息

通过reflect.TypeOf()从结构体中获取反射类型对象reflect.Type,可通过reflect.Type获取结构体成员信息。

结构体成员访问方法列表,与成员获取相关的reflect.Type的方法。

方法返回值描述
type.Field(index int)StructField根据索引获取对应结构体字段
type.NumField()int获取结构体成员字段数量
type.FieldByName(name string)(StructField, bool)根据指定字符串获取对应的结构体字段
type.FieldByIndex(index []int)StructField多层成员访问,根据[]int提供的结构体字段索引获取字段信息。
type.FieldByNameFunc(match func(string) bool)(StructField, bool)根据匹配函数获取字段

例如:通过反射值对象reflect.Value获取结构体成员信息

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
}

func main() {
    user := &User{Id: 1, Name: "root"}

    val := reflect.ValueOf(user)
    fmt.Printf("%v\n", val) //&{1 root}

    elem := val.Elem()
    fmt.Printf("%v\n", elem) //{1 root}

    elemType := elem.Type()
    fmt.Printf("%v\n", elemType) //main.User

    numField := elem.NumField()
    fmt.Printf("%v\n", numField) //2

    for i := 0; i < numField; i++ {
        fieldName := elemType.Field(i).Name

        field := elem.Field(i)
        fmt.Printf("%v\n", field)

        fieldType := field.Type()
        fieldValue := field.Interface()

        fmt.Printf("%d: %s %s = %v\n", i, fieldName, fieldType, fieldValue)
    }
}
&{1 root}
{1 root}
main.User
2
1
0: Id int = 1
root
1: Name string = root

StructField

  • 结构体字段类型

reflect.TypeField()方法返回StructField结构,描述了结构体的成员信息,可获取成员与结构体的关系,比如偏移、索引、是否为匿名字段、结构体标签等。

type StructField struct{
  Name string //字段名
  PkgPath string //字段路径
  Type Type //字段反射类型对象
  Tag StructTag//字段的结构体标签
  Offset uintptr //字段在结构体中的相对偏移
  Index []int //Type.FieldByIndex返回的索引值
  Anonymous bool//是否为匿名字段
}
字段类型描述
Namestring字段名称
PkgPathstring字段在结构体中的路径
TypeType字段本身的反射类型对象,类型为reflect.Type。
TagStructTag结构体标签,为结构体字段标签的额外信息,可单独提取。
IndexFieldByIndex索引顺序
Anonymousbool字段是否为匿名字段
  • 获取结构体成员反射信息

例如:实例化结构体后遍历成员,通过reflect.TypeFieldByName()获取字段信息。

//声明结构体
type User struct {
    Id   int
    Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//遍历结构体成员
for i := 0; i < typ.NumField(); i++ {
    //获取成员
    field := typ.Field(i)
    //获取成员属性
    name := field.Name
    tag := field.Tag
    fmt.Printf("name = %v, tag = %v\n", name, tag)
}
//声明结构体
type User struct {
    Id   int
    Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
    tag := field.Tag
    tagName := tag.Get("json")
    tagId := tag.Get("id")
    fmt.Printf("name = %v, id = %v\n", tagName, tagId) //name = name, id = 100
}

StructTag

  • 结构体标签

通过reflect.Type获取结构体成员reflect.StructField结构中的Tag即结构体标签StructTag

结构体标签是对结构体字段的额外信息标签

JSON、BSON等格式进行序列化,ORM关系对象映射都会使用到结构体标签,使用标签设定字段在处理时应具备的特殊属性和可能发生的行为。这些信息都是静态的,无需实例化结构体可通过反射获取到。

结构体标签书写格式:key1:"value1" key2:"value2"

结构体标签由一个或多个键值对组成,键与值使用冒号分割,值使用双引号包裹,键值对之间使用空格分割。

从结构体标签中获取值

StructTag拥有方法可以对Tag信息进行解析和提取

  • Get方法根据Tag中的键获取对应的值
func (tag StructTag) Get(key string) string
  • Lookup方法根据Tag中的键查询值是否存在
func (tag StructTag) Lookup(key string) (value string, ok bool)

例如:

//声明结构体
type User struct {
    Id   int    `json:"id" bson:"id"`
    Name string `json:"name" bson:"name" orm:"size(32)"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
    tag := field.Tag

    val, ok := tag.Lookup("bson")
    if ok {
        fmt.Printf("bson = %v\n", val) //bson = name
    }

    val = tag.Get("json")
    fmt.Printf("json = %v\n", val) //json = name
}

方法

Golang中反射是由reflect包支持的,reflect包提供了两个重要的类型TypeValue。任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成。

反射是用来检测存储在接口变量内部(值 value, 类型 concrete type)pair对的一种机制,reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的ValueType

  • reflect.ValueOf用于获取输入参数接口中的数据的值,若接口为空则返回0。
func reflect.ValueOf(i interface{}) reflect.Value {...}
  • reflect.TypeOf用于动态获取输入参数接口中值的类型,若接口为空则返回nil
func reflect.TypeOf(i interface{}) reflect.Type {...}

reflect.TypeOf()用于获取pair(value, type)中的typereflect.ValueOf()获取获取pair(value, type)中的value

例如:将float64类型的变量作为原值传递给reflect.TypeOf(i interface{})获取其类型信息

var num float64 = 3.14156
t.Log(reflect.ValueOf(num)) //3.14156
t.Log(reflect.TypeOf(num))  //float64

例如:

type Response struct {
    Code    int
    Message string
}

func TestReflect(t *testing.T) {
    var response Response
    t.Log(reflect.ValueOf(response)) //{0 }
    t.Log(reflect.TypeOf(response))  //test.Response
}

ValueOf

func reflect.ValueOf(rawValue interface{}) reflect.Value

例如:

val := reflect.ValueOf(nil)
fmt.Printf("%v\n", val) //<invalid reflect.Value>

例如:动态调用无参数的结构体方法

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type User struct {
    Id   int
    Name string
}

func (u *User) Do() {
    fmt.Printf("User Do...\n")
}

func TestReflect(t *testing.T) {
    ins := &User{Id: 1, Name: "root"}
    val := reflect.ValueOf(ins)

    fname := "Do"
    val = val.MethodByName(fname)

    val.Call(nil)
}

例如:动态调用结构体带参数的方法

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type User struct {
    Id   int
    Name string
}

func (u *User) Do(id int, msg string) {
    fmt.Printf("%v %v\n", id, msg)
}

func TestReflect(t *testing.T) {
    ins := &User{Id: 1, Name: "root"}
    val := reflect.ValueOf(ins)

    fname := "Do"
    val = val.MethodByName(fname)

    id := reflect.ValueOf(1)
    msg := reflect.ValueOf("hello")
    in := []reflect.Value{id, msg}
    val.Call(in)
}

Value

reflect.Value
  • reflect.ValueOf(rawValue interface)原值会转换为类型为reflect.Value的反射值对象。
type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}
  • reflect.Value反射值对象可与原值通过包装和值获取相互转化

通过reflect.Value重新获取原始值

方法返回值描述
Interface()interface{}通过类型断言转换为指定类型
Int()int64将值以int类型返回
Uint()uint64将值以uint类型返回
Float()float64将值以双精度类型返回
Bool()bool将值以bool类型返回
Bytes()[]byte将值以字节数组类型返回
String()string将值以字符串类型返回

例如:

var i int = 1024

val := reflect.ValueOf(i)

var j int = val.Interface().(int)
fmt.Printf("j = %v\n", j)

var k int = int(val.Int())
fmt.Printf("k = %v\n", k)
  • 反射值对象reflect.Value对零值和空进行有效性判断
方法返回值描述
value.IsNil ()bool判断返回值是否为nil
value.IsValid()bool判断返回值是否合法
func (v Value) IsNil() bool

若值类型不是通道、函数、接口、映射、指针、切片时会发生panic,类似语言层面的v == nil操作。

func (v Value) IsValid() bool

若值本身非法则返回false

例如:判断空指针

var ptr *int
isNil := reflect.ValueOf(ptr).IsNil()
fmt.Printf("%v\n", isNil) //true

例如:判断nil是否非法

isValid := reflect.ValueOf(nil).IsValid()
fmt.Printf("%v\n", isValid) //false
  • 通过反射修改变量的值

使用reflect.Value对包装的值进行修改时,需遵循一定规则,若没有按照规则进行代码设计和编写,则无法修改对象的值或运行时发生宕机。

反射值对象的判定及获取元素的方法

方法返回值描述
value.Elem()reflect.Value获取值指向的元素值,类似*操作,当值类型不是指针或接口时会发生宕机,空指针返回nilValue
value.Addr()reflect.Value对可寻址的值返回其地址,类似&操作,但值不可寻址时发生宕机。
value.CanAddr()bool值是否可寻址
value.CanSet()bool值是否可被修改,要求值可寻址且是可导出的字段。

Type

reflect.Type

根据传入的结构体生成SQL语句

func BuildInsertSQL(obj interface{}) string {
    val := reflect.ValueOf(obj)
    typ := reflect.TypeOf(obj)
    //判断参数是否为结构体
    if val.Kind() != reflect.Struct {
        panic("unsupported type")
    }
    //获取结构体名作为数据表名
    typename := typ.Name()
    //遍历结构体字段
    var cols []string
    var vals []string
    for i := 0; i < val.NumField(); i++ {
        //获取字段名称
        col := typ.Field(i).Name
        cols = append(cols, fmt.Sprintf("`%s`", col))
        //获取字段值
        var v string
        field := val.Field(i)
        switch field.Kind() {
        case reflect.String:
            v = field.String()
        case reflect.Int:
            v = strconv.FormatInt(field.Int(), 10)
        default:
        }
        vals = append(vals, v)
    }
    //转换为字符串
    colstr := strings.Join(cols, ",")
    valstr := strings.Join(vals, ",")
    //拼接SQL
    query := fmt.Sprintf("INSERT INTO `%s`(%s) VALUES(%s)", typename, colstr, valstr)
    return query
}
type User struct {
    id   int
    name string
}
user := User{id: 1, name: "admin"}
sql := BuildInsertSQL(user)
fmt.Println(sql)
INSERT INTO `User`(`id`,`name`) VALUES(1,admin)

Elem

  • 指针与指针指向的元素

Golang对指针获取反射对象时,通过reflect.Elem()方法获取指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作。


Golang反射中对指针变量的种类都是Ptr

package test

import (
    "fmt"
    "reflect"
    "testing"
)

//声明空结构体
type User struct {
}

func TestReflect(t *testing.T) {
    //创建指针变量
    ins := &User{}
    //获取指针变量的反射类型信息
    typ := reflect.TypeOf(ins)

    //获取指针变量的类型名称
    name := typ.Name()
    //获取指针变量的种类
    kind := typ.Kind()
    fmt.Printf("name = %v, kind = %v\n", name, kind) //name = , kind = ptr

    //获取指针变量的元素类型
    elem := typ.Elem()
    //获取指针类型元素的名称
    name = elem.Name()
    //获取指针类型元素的种类
    kind = elem.Kind()
    fmt.Printf("name = %v, kind = %v\n", name, kind) //name = User, kind = struct
}

value.Elem()方法会返回反射值对象所包含的值或指针指向的值,若反射值对象的种类Kind不是接口或指针,则会发生panic。若反射值为0则返回零值。

ptr := (*int)(nil)

val := reflect.ValueOf(ptr)
fmt.Printf("%v\n", val) //<nil>

elem := val.Elem()
fmt.Printf("%v\n", elem) //<invalid reflect.Value>

isValid := elem.IsValid()
fmt.Printf("%v\n", isValid) //false

Kind

编程中使用最多的是类型(Type),反射中当需要区别一个大品种的类型时使用种类(Kind)。

例如:需要统一判断类型中的指针时,使用种类(Kind)较为方便。

Golang中类型(Type)是指系统原生的数据类型,比如intstringboolfloat32等,以及使用type关键字定义的类型,这些类型的名称就是其类型本身的名称。

种类(Kind)指的是对象归属的品种

reflect包中定义的种类Kind

type Kind uint
种类常量描述
Invalid Kind非法类型
Bool布尔型
Int有符号整型
Int8有符号8位整型
Int16有符号16位整型
Int32有符号32位整型
Uint无符号整型
Uint8无符号8位整型
Uint16无符号16位整型
Uint32无符号32位整型
Uint64无符号64位整型
Uintptr指针
Float32单精度浮点数
Float64双精度浮点数
Complex6464位复数类型
Complet128128位复数类型
Array数组
Chan通道
Func函数
Interface接口
Map映射
Ptr指针
Slice切片
String字符串
Struct结构体
UnsafePointer底层指针

Map、Slice、Chan属于引用类型,使用起来类似指针,但在种类中仍属于独立的种类,而不属于Ptr。

例如:

type User struct{}

User结构体属于Struct种类,*User属于Ptr种类。


从类型对象reflect.Type中获取类型名称Name和种类Kind

  • 类型名称对应的反射获取方法是reflect.Type中的Name()方法,返回表示类型名称的字符串。
  • 类型归属的种类Kind使用的是reflect.Type中的Kind()方法,返回reflect.Kind类型的常量。

例如:从常量中获取类型信息

package test

import (
    "fmt"
    "reflect"
    "testing"
)

//定义枚举类型
type Enum int

//定义常量
const (
    Zero Enum = 0
)

func TestReflect(t *testing.T) {
    typ := reflect.TypeOf(Zero)
    name := typ.Name()                               //Enum
    kind := typ.Kind()                               //int
    fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Enum, Kind = int
}

例如:从结构体中获取类型信息

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type Response struct {
    Code    int
    Message string
}

func TestReflect(t *testing.T) {
    //结构体实例
    response := Response{}
    //获取结构体实例的反射类型对象
    typ := reflect.TypeOf(response)
    //获取反射类型对象的名称和种类
    name := typ.Name()                               //Response
    kind := typ.Kind()                               //struct
    fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Response, Kind = struct
}

Implements

  • 判断实例是否已实现接口

创建接口

type IT interface {
    test()
}

创建结构体并实现接口方法

type T struct {
}

func (t *T) test() {
    fmt.Printf("T struct test run...\n")
}

判断结构体是否实现接口

t1 := reflect.TypeOf(&T{})
t2 := reflect.TypeOf((*IT)(nil)).Elem()

ok := t1.Implements(t2)
fmt.Printf("%v\n", ok)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值