Golang 学习笔记(11)—— 反射

转载请注明出处:Golang 学习笔记(11)—— 反射

Golang

介绍

反射是程序执行时检查其所拥有的结构。尤其是类型的一种能力。这是元编程的一种形式。它同一时候也是造成混淆的重要来源。

每一个语言的反射模型都不同(同一时候很多语言根本不支持反射)。本节将试图明白解释在 Go 中的反射是怎样工作的

实现

package

import "reflect"

Type和Value

首先,reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。

Type就是定义的类型的一个数据类型,Value是值的类型

下面分别对Type和Value做个简单介绍一下

Type

Type类型用来表示一个go类型。

不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。

获取Type对象的方法:

func TypeOf(i interface{}) Type

例如:

str := "this is string"
type := reflect.TypeOf(str)

Type中常用的方法:

  • Kind() Kind
    Kind返回该接口的具体分类
    Kind代表Type类型值表示的具体分类。零值表示非法分类。
const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)
  • Name() string
    Name返回该类型在自身包内的类型名,如果是未命名类型会返回""

  • PkgPath() string
    PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""

  • NumField() int
    返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic

  • Field(i int) StructField
    返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic

  • FieldByIndex(index []int) StructField
    返回索引序列指定的嵌套字段的类型,
    等价于用索引中每个值链式调用本方法,如非结构体将会panic

  • FieldByName(name string) (StructField, bool)
    返回该类型名为name的字段(会查找匿名字段及其子字段),
    布尔值说明是否找到,如非结构体将panic

  • type StructField

type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}

StructField类型描述结构体中的一个字段的信息。

  • NumMethod() int
    返回该类型的方法集中方法的数目
    匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
    匿名字段导致的歧义方法会滤除

  • Method(int) Method
    返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
    对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

  • MethodByName(string) (Method, bool)
    根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
    对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

  • type Method

type Method struct {
    // Name是方法名。PkgPath是非导出字段的包路径,对导出字段该字段为""。
    // 结合PkgPath和Name可以从方法集中指定一个方法。
    Name    string
    PkgPath string
    Type  Type  // 方法类型
    Func  Value // 方法的值
    Index int   // 用于Type.Method的索引
}

Method代表一个方法。

Value

Value为go值提供了反射接口。

不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。

Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回<invalid Value>,所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。

获取Value对象的方法:

func ValueOf(i interface{}) Value

ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。

例如:

str := "this is string"
val := reflect.ValueOf(str)

** Value中常用的方法:**

  • Kind()
func (v Value) Kind() Kind

Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid

  • func (Value) IsValid
func (v Value) IsValid() bool

IsValid返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

  • func (Value) IsNil
func (v Value) IsNil() bool

IsNil报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。注意IsNil并不总是等价于go语言中值与nil的常规比较。例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。

  • func (Value) Type
func (v Value) Type() Type

返回v持有的值的类型的Type表示。

  • func (Value) Elem
func (v Value) Elem() Value

Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

  • func (Value) NumField
func (v Value) NumField() int

返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic

  • func (Value) Field
func (v Value) Field(i int) Value

返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic

  • func (Value) FieldByIndex
func (v Value) FieldByIndex(index []int) Value

返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic

  • func (Value) FieldByName
func (v Value) FieldByName(name string) Value

返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。

  • func (Value) NumMethod
func (v Value) NumMethod() int

返回v持有值的方法集的方法数目。

  • func (Value) Method
func (v Value) Method(i int) Value

返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。

  • func (Value) MethodByName
func (v Value) MethodByName(name string) Value

返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值。

  • func (Value) Call
func (v Value) Call(in []Value) []Value

Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。如果v持有值是可变参数函数,Call方法会自行创建一个代表可变参数的切片,将对应可变参数的值都拷贝到里面。

  • func (Value) CallSlice
func (v Value) CallSlice(in []Value) []Value

CallSlice调用v持有的可变参数函数,会将切片类型的in[len(in)-1](的成员)分配给v的最后的可变参数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值,可变参数函数的可变参数位置提供一个切片并跟三个点号代表"解切片")。如果v的Kind不是Func或者v的持有值不是可变参数函数,会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。

  • func (Value) Interface
func (v Value) Interface() (i interface{})

本方法返回v当前持有的值(表示为/保管在interface{}类型),等价于:

var i interface{} = (v's underlying value)

如果v是通过访问非导出结构体字段获取的,会导致panic。
另外还有很多类似这个函数的功能可以获取value的值。

func (v Value) String() string
func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) Float() float64
func (v Value) Complex() complex128
func (v Value) Bytes() []byte
...
  • func (Value) CanSet
func (v Value) CanSet() bool

如果v持有的值可以被修改,CanSet就会返回真。只有一个Value持有值可以被寻址同时又不是来自非导出字段时,它才可以被修改。如果CanSet返回假,调用Set或任何限定类型的设置函数(如SetBool、SetInt64)都会panic。

  • 设置函数
func (v Value) SetBool(x bool)
func (v Value) SetInt(x int64)
func (v Value) SetUint(x uint64)
func (v Value) SetFloat(x float64)
func (v Value) SetComplex(x complex128)
func (v Value) SetBytes(x []byte)
func (v Value) SetString(x string)
...

例子

说了这么多,看下实例吧
对象:

package demo

import (
    "fmt"
)

type Address struct{
    City string
    Area string
}

type Student struct{
    Address
    Name string
    Age int
}

func (this Student) Say(){
    fmt.Println("hello, i am ", this.Name, "and i am ", this.Age)
}

func (this Student) Hello(word string){
    fmt.Println("hello", word, ". i am ", this.Name)
}

有关反射函数demo

package demo

import (
    "fmt"
    "reflect"
)

/*
  获取对象的信息
*/
func StructInfo(o interface{}){
    //获取对象的类型
    t := reflect.TypeOf(o)
    fmt.Println(t.Name(), "object type: ", t.Name())

    if k := t.Kind(); k != reflect.Struct{
        fmt.Println("the object is not a struct, but it is", t.Kind())
        return
    }

    //获取对象的值
    v := reflect.ValueOf(o)
    fmt.Println(t.Name(), "object value: ", v)

    //获取对象的字段
    fmt.Println(t.Name(), "fields: ")
    for i := 0; i < t.NumField(); i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s:%v = %v \n", f.Name, f.Type, val)
        //通过递归调用获取子类型的信息
        t1 := reflect.TypeOf(val)
        if k := t1.Kind(); k == reflect.Struct{
            StructInfo(val)
        }
    }
    //获取对象的函数
    fmt.Println(t.Name(), "methods: ", t.NumMethod())
    for i := 0; i < t.NumMethod(); i++{
        m := t.Method(i)
        fmt.Printf("%10s:%v \n", m.Name, m.Type)
    }
}

/*
  匿名字段的反射
*/
func Annoy(o interface{}){
    t := reflect.TypeOf(o)
    for i := 0; i < t.NumField(); i++{
        f := t.Field(i)
        fmt.Printf("%10s:%#v \n", f.Name, f)
    }
}

/*
  通过反射设置字段
*/
func ReflectSet(o interface{}){
    v := reflect.ValueOf(o)
    if v.Kind() == reflect.Ptr && !v.Elem().CanSet(){
        fmt.Println("修改失败")
        return
    }
    v = v.Elem()
    //获取字段
    f := v.FieldByName("Name")
    if !f.IsValid(){
        fmt.Println("修改失败")
        return
    }
    //设置值
    if f.Kind() == reflect.String{
        f.SetString("chairis")
    }
}

/* 
  通过反射调用函数
*/
func ReflectMethod(o interface{}){
    v := reflect.ValueOf(o)
    //无参函数调用
    m1:= v.MethodByName("Say")
    m1.Call([]reflect.Value{})

    //有参函数调用
    m2 := v.MethodByName("Hello")
    m2.Call([]reflect.Value{reflect.ValueOf("iris")})
}

上述代码一共有以下这几个函数:
StructInfo:是用来说明type, 和value的使用方式的按照以下方式调用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    StructInfo(stu)
}

结果如下:


运行结果

Annoy : 用来展示匿名字段的函数。
照以下方式调用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    Annoy(stu)
}

结果如下:


运行结果

ReflectSet : 是用来实现通过反射来修改字段值的函数。
按照以下调用:

package main

import (
    "fmt"
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    fmt.Println("before: ", stu)
    ReflectSet(&stu)
    fmt.Println("after: ", stu)
}

结果如下:


运行结果

ReflectMethod : 是用来实现通过反射调用函数的方法。
按照以下调用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    ReflectMethod(stu)
}

得以下结果:


运行结果

源码

github 源码地址

转载请注明出处:Golang 学习笔记(11)—— 反射

目录
上一节:Golang 学习笔记(10)—— mysql操作
下一节:Golang 学习笔记(12)—— ORM实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容