Go reflect

JunChow520 · · 560 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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//是否为匿名字段
}
字段 类型 描述
Name string 字段名称
PkgPath string 字段在结构体中的路径
Type Type 字段本身的反射类型对象,类型为reflect.Type。
Tag StructTag 结构体标签,为结构体字段标签的额外信息,可单独提取。
Index FieldByIndex 索引顺序
Anonymous bool 字段是否为匿名字段
  • 获取结构体成员反射信息

例如:实例化结构体后遍历成员,通过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 typepair对的一种机制,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.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

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 双精度浮点数
Complex64 64位复数类型
Complet128 128位复数类型
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)

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:JunChow520

查看原文:Go reflect

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

560 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传