第十九章:Go语言反射

golang-gopher.png

1. 概述

Go语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

2. 反射类型对象

使用reflect.TypeOf() 函数获取任意变量的类型对象 reflect.Type

**函数的源码如下 : **

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
  eface := *(*emptyInterface)(unsafe.Pointer(&i))
  return toType(eface.typ)
}

通过获取的类型对象就能访问原变量的类型信息

在类型信息中我们需要知道 类型(Type)种类(Kind)的区别

类型 Type : 通常指的是系统中的原生数据类型和使用type 关键字定义的类型,通过类型对象 reflect.Type中的Name() 方法获取

种类Kind : 指定的对象的根本种类,是更高一层的概括,通过reflect.Type 中的 Kind() 函数获取

  • Type的值和Kind的值可能相同,也可能不相同
package main

import (
    "fmt"
    "reflect"
)

type char string
type dogs struct {
}

func main() {
    var c char
    Str := "golang go.."
    // 获取C的类型对象
    TypeOfC := reflect.TypeOf(c)
    // Name() 获取类型
    // Kind() 获取种类
    fmt.Println("Type = ", TypeOfC.Name(), "Kind = ", TypeOfC.Kind()) // Type =  char Kind =  string
    Golden := dogs{}
    TypeOfGolden := reflect.TypeOf(Golden)
    fmt.Println("Type = ", TypeOfGolden.Name(), "Kind = ", TypeOfGolden.Kind()) // Type =  char Kind =  string
    TypeOfStr := reflect.TypeOf(Str)
    fmt.Println("Type = ", TypeOfStr.Name(), "Kind = ", TypeOfStr.Kind()) //Type =  string Kind =  string

    huntaway := &dogs{}   // 指针变量
    TypeOfHuntaway := reflect.TypeOf(huntaway)
    // Go语言中所有的指针变量种类都是 `ptr`
    // 指针变量的类型是此时是空
    fmt.Println("Type = ", TypeOfHuntaway.Name(), "Kind = ", TypeOfHuntaway.Kind()) // Type =   Kind =  ptr
    // 对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类
    TypeOfHuntaway = TypeOfHuntaway.Elem()
    fmt.Println("Type = ", TypeOfHuntaway.Name(), "Kind = ", TypeOfHuntaway.Kind()) // Type =  dogs Kind =  struct 
}

go run main.go

Type =  char Kind =  string
Type =  dogs Kind =  struct
Type =  string Kind =  string
Type =   Kind =  ptr
Type =  dogs Kind =  struct

当一个变量是结构体实例的时候,怎么通过反射获取类型信息呢?

通过 reflect 包中reflect.typeField() FieldByIndex FieldByName FieldByNameFunc 方法获取的 StructField 结构体有些什么内容呢?

// A StructField describes a single field in a struct.
type StructField struct {
  // Name is the field name.
  Name string
  // PkgPath is the package path that qualifies a lower case (unexported)
  // field name. It is empty for upper case (exported) field names.
  // See https://golang.org/ref/spec#Uniqueness_of_identifiers
  PkgPath string

  Type      Type      // field type
  Tag       StructTag // field tag string
  Offset    uintptr   // offset within struct, in bytes
  Index     []int     // index sequence for Type.FieldByIndex
  Anonymous bool      // is an embedded field  
}
package main

import (
    "fmt"
    "reflect"
)

type dogs struct {
    Name  string `json:"name"`
    Age int8
    T int `json:"t" id:"99"`
}
func main(){
    // 创建实例
    Hachiko := dogs{Name:"Hachiko",Age:int8(2),T:66}
    //获取反射对象实例
    typeD := reflect.TypeOf(Hachiko)
    // NumField()函数,返回结构体成员的数量
    for i:=0;i<typeD.NumField();i++{
        // 获取结构体成员的类型
        // Field()函数 根据索引返回结构体对应field的信息
        typeOfField := typeD.Field(i)
        // 输出成员字段名称和tag(标签信息),成员类型
        fmt.Printf("%s , %v,%s\n",typeOfField.Name,typeOfField.Tag,typeOfField.Type)
    }
    // 通过结构体字段名获取其类型信息
    // FieldByName()函数,根据字段名返回字段信息
    if fieldType,ok := typeD.FieldByName("T");ok{
        fmt.Println(fieldType.Tag.Get("json"),fieldType.Tag.Get("id"))
    }
}

go run main.go

Name , json:"name",string
Age , ,int8
T , json:"t" id:"99",int
t 99

3. 反射的值对象

反射可以动态的获取或者设置变量的值
Go语言中使用reflect.Value获取和设置变量的值

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a int = 99
    fmt.Printf("a ==> %T,%v\n", a, a)
    // 使用reflect.ValueOf()函数获取反射值对象
    valueOfA := reflect.ValueOf(a)
    // 获取的反射值对象,再通过值对象的Interface()方法获取原值
    var b int = valueOfA.Interface().(int)
    fmt.Printf("b ==> %T,%v\n", b, b)
    // 反射对象的Int()方法获取int64类型值,然后强制转换成int32位
    var c int32 = int32(valueOfA.Int())
    fmt.Printf("c ==> %T,%v\n", c, c)
}

go run main.go

a ==> int,99
b ==> int,99
c ==> int32,99
package main

import (
    "fmt"
    "reflect"
)

type demo struct {
    a int
    b string
    c bool
    float64
    d [5]int
}

func (d *demo) dF1() {
    fmt.Println(d.a)
}
func (d *demo) dF2() {
    fmt.Println(d.b)
}

func main() {
    t := demo{99, "golang", true, 98.90, [5]int{1, 2, 3, 5, 6}}
    // 获取值对象
    valueOfT := reflect.ValueOf(t)
    // NumField()是获取字段数量
    fmt.Println(valueOfT.NumField()) // 5
    // 获取索引为1的字段
    fieldOf1 := valueOfT.Field(1)
    // 打印该值对象的类型
    fmt.Println(fieldOf1.Type()) // string
    // 通过字段名查找
    fieldOfd := valueOfT.FieldByName("d")
    fmt.Println(fieldOfd.Type()) // [5]int
}

go run main.go

5
string
[5]int

4. 反射修改值

reflect.Value 也提供了修改版值的方法

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var age int8 = 99
    // 获取一个值对象
    valueOfAge := reflect.ValueOf(&age)
    // Elem() 对可寻值的元素获取它的值
    // Addr() 对可寻址的元素获取它地址
    // CanSet() bool 返回元素(值对象)是否能被设置
    // CanAddr() bool 返回元素(值对象)是否能被寻址
    valueOfAge = valueOfAge.Elem()
    valueOfAgeAddr := valueOfAge.Addr()
    fmt.Println(valueOfAge) // 99
    fmt.Println(valueOfAgeAddr) // 0xc000054080
    fmt.Println(valueOfAge.CanSet()) // true
    fmt.Println(valueOfAge.CanAddr()) // true
    // SetInt() 使用int64设置值
    // SetUint() 使用uint64设置值
    // SetFloat() 使用float64设置值
    // SetBool() 使用bool设置值
    // SetBytes() 设置字节数组[]bytes值
    // SetString 设置字符串值
    valueOfAge.SetInt(1)
    fmt.Printf("age type= %T, value= %v",age,age)
}

go run main.go

99
0xc000054080
true
true
age type= int8, value= 15

通过类型创建类型实例

package main

import (
    "fmt"
    "reflect"
)

func main(){
    var i  int = 99
    // 获取反射类型对象
    typeOfI := reflect.TypeOf(i)
    // 根据反射类型对象创建类型实例
    newI := reflect.New(typeOfI)
    // 答应类型和种类
    fmt.Println(newI.Type(),newI.Kind()) //*int ptr
}


5. 综合Demo

package main

import (
    "fmt"
    "log"
    "reflect"
)

type Person struct {
    Name string `json:"name"`
    Age int  `json:"age"`
    Sex string  `json:"sex"`
}
func (p Person) PersonSet(name string,age int,sex string){
    p.Name = name
    p.Age = age
    p.Sex = sex
    fmt.Println(p)
}
func (p Person) ShowPerson(){
    fmt.Println(p)
}
func reflectOfStruct(a interface{}){
    // 获取reflect.Type类型
    typeObj := reflect.TypeOf(a)
    // 获取reflect.Value类型
    valueObj := reflect.ValueOf(a)
    // 获取Kind类别,下面两种方法都能获取
    KindType := typeObj.Kind()
    //KindValue := valueObj.Kind()
    //fmt.Println(KindType)
    //fmt.Println(KindValue)
    if KindType != reflect.Struct{
        log.Fatal("Kind is not error")
        return
    }
    // 获取字段数量
    fieldsNum := valueObj.NumField()
    for i:=0;i<fieldsNum;i++{
        fmt.Printf("field %d %v\n",i,valueObj.Field(i))
        // 获取指定的标签值
        tagValue := typeObj.Field(i).Tag.Get("json")
        if tagValue != ""{
            fmt.Printf("field %d tag = %v\n",i,tagValue)
        }
    }
    // 获取方法数量
    MethodNum := valueObj.NumMethod()
    fmt.Printf("has %d methods\n",MethodNum)
    // 调用第一个方法
    valueObj.Method(1).Call(nil)
    // 对有参数的方法调用
    var params []reflect.Value
    params = append(params,reflect.ValueOf("lisi"))
    params = append(params,reflect.ValueOf(88))
    params = append(params,reflect.ValueOf("man"))
    // 传递参数,调用指定方法名的方法
    valueObj.MethodByName("PersonSet").Call(params)
}
func main() {
    var a Person = Person{
        Name:"zhangsan",
        Age:99,
    }
    // 调用函数
    reflectOfStruct(a)
}

go run main.go

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

推荐阅读更多精彩内容

  • [TOC] Golang的反射reflect深入理解和示例 【记录于2018年2月】 编程语言中反射的概念 在计算...
    AllenWu阅读 868评论 1 14
  • 此篇文章引自https://juejin.im/post/5a75a4fb5188257a82110544#hea...
    北春南秋阅读 3,204评论 1 0
  • fmt格式化字符串 格式:%[旗标][宽度][.精度][arg索引]动词旗标有以下几种:+: 对于数值类型总是输出...
    皮皮v阅读 1,099评论 0 3
  • Go语言做Web编程非常方便,并且在开发效率和程序运行效率方面都非常优秀。相比于Java,其最大的优势就是简便易用...
    暗黑破坏球嘿哈阅读 9,006评论 6 66
  • 1)Export单独导出IPA文件至本地端 2)通过ApplicationLoader工具添加当前应用的spa文件...
    衹氏阅读 91评论 0 0