方法和类型的反射

反射是应用程序检查其所拥有的结构,尤其是类型的一种能。每种语言的反射模型都不同,并且有些语言根本不支持反射。Go语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,即可从运行时态的示例对象反求其编码阶段的定义,标准库中reflect包提供了相关的功能。在reflect包中,通过reflect.TypeOf()reflect.ValueOf()分别从类型、值的角度来描述一个Go对象。

  1. func TypeOf(i interface{}) Type
  2. type Type interface
  3. func ValueOf(i interface{}) Value
  4. type Value struct

在Go语言的实现中,一个interface类型的变量存储了2个信息, 一个<值,类型>对,<value,type> :

  1. (value, type)

value是实际变量值,type是实际变量的类型。两个简单的函数,reflect.TypeOfreflect.ValueOf,返回被检查对象的类型和值。

例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64reflect.ValueOf(x) 返回 3.4。实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

  1. func TypeOf(i interface{}) Type
  2. func ValueOf(i interface{}) Value

reflect.Typereflect.Value 都有许多方法用于检查和操作它们。

Type主要有:

  • Kind() 将返回一个常量,表示具体类型的底层类型
  • Elem()方法返回指针、数组、切片、字典、通道的基类型,这个方法要慎用,如果用在其他类型上面会出现panic

Value主要有:

  • Type()将返回具体类型所对应的 reflect.Type(静态类型)
  • Kind() 将返回一个常量,表示具体类型的底层类型
  • Elem()返回接口所包含的值,或者,指针指向的值

反射可以在运行时检查类型和变量,例如它的大小、方法和动态的调用这些方法。这对于没有源代码的包尤其有用。

由于反射是一个强大的工具,但反射对性能有一定的影响,除非有必要,否则应当避免使用或小心使用。下面代码针对int、数组以及结构体分别使用反射机制,其中的差异请看注释。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. name string
  8. }
  9. func main() {
  10. var a int = 50
  11. v := reflect.ValueOf(a) // 返回Value类型对象,值为50
  12. t := reflect.TypeOf(a) // 返回Type类型对象,值为int
  13. fmt.Println(v, t, v.Type(), t.Kind()) // 50 int int int
  14. var b [5]int = [5]int{5, 6, 7, 8}
  15. fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int
  16. var Pupil Student
  17. p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
  18. fmt.Println(p.Type()) // 输出:main.Student
  19. fmt.Println(p.Kind()) // 输出:struct
  20. }

在Go语言中,类型包括 static typeconcrete type。简单说 static type 是你在编码是看见的类型(如intstring),concrete type 是实际具体的类型,runtime 系统看见的类型

Type()返回的是静态类型,而 Kind() 返回的是具体类型。上面代码中,在 int,数组以及结构体三种类型情况中,可以看到 kind()type()返回值的差异。

通过反射可以修改原对象

  • CanAddr()方法:判断它是否可被取地址

  • CanSet()方法:判断它是否可被取地址并可被修改

    是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性

通过一个settableValue反射对象来访问、修改其对应的变量值:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. name string
  8. Age int
  9. }
  10. func main() {
  11. var a int = 50
  12. v := reflect.ValueOf(a) // 返回Value类型对象,值为50
  13. t := reflect.TypeOf(a) // 返回Type类型对象,值为int
  14. fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem()) // 50 int int int 50
  15. seta := reflect.ValueOf(&a).Elem() // 这样才能让seta保存a的值
  16. fmt.Println(seta, seta.CanSet()) // 50 true
  17. seta.SetInt(1000)
  18. fmt.Println(seta) // 1000
  19. var b [5]int = [5]int{5, 6, 7, 8}
  20. fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem()) // [5]int array int
  21. var Pupil Student = Student{"joke", 18}
  22. p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
  23. fmt.Println(p.Type()) // 输出:main.Student
  24. fmt.Println(p.Kind()) // 输出:struct
  25. setStudent := reflect.ValueOf(&Pupil).Elem()
  26. //setStudent.Field(0).SetString("Mike") // 未导出字段,不能修改,panic会发生
  27. setStudent.Field(1).SetInt(19)
  28. fmt.Println(setStudent) // {joke 19}
  29. }

虽然反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,但不能修改这些未导出的成员。因为一个结构体中只有被导出的字段才是可修改的(就是对外可见的才可以修改)

v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)。然后这个是返回的是指针,也是不可修改的,需要再调用 Elem() 函数,即setx = reflect.ValueOf(&x).Elem(),假如 xint 类型的,然后可以setx.SetInt(111)这样修改了

在结构体中有tag标签,通过反射可获取结构体成员变量的tag信息。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Student struct {
  7. name string
  8. Age int `json:"years"`
  9. }
  10. func main() {
  11. var Pupil Student = Student{"joke", 18}
  12. setStudent := reflect.ValueOf(&Pupil).Elem()
  13. sSAge, _ := setStudent.Type().FieldByName("Age")
  14. fmt.Println(sSAge.Tag.Get("json")) // years
  15. }

反射结构体

为了完整说明反射的情况,通过反射一个结构体类型,综合来说明。下面例子较为系统地利用一个结构体,来充分举例说明反射:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. // 结构体
  7. type ss struct {
  8. int
  9. string
  10. bool
  11. float64
  12. }
  13. func (s ss) Method1(i int) string { return "结构体方法1" }
  14. func (s *ss) Method2(i int) string { return "结构体方法2" }
  15. var (
  16. structValue = ss{ // 结构体
  17. 20,
  18. "结构体",
  19. false,
  20. 64.0,
  21. }
  22. )
  23. // 复杂类型
  24. var complexTypes = []interface{}{
  25. structValue, &structValue, // 结构体
  26. structValue.Method1, structValue.Method2, // 方法
  27. }
  28. func main() {
  29. // 测试复杂类型
  30. for i := 0; i < len(complexTypes); i++ {
  31. PrintInfo(complexTypes[i])
  32. }
  33. }
  34. func PrintInfo(i interface{}) {
  35. if i == nil {
  36. fmt.Println("--------------------")
  37. fmt.Printf("无效接口值:%v\n", i)
  38. fmt.Println("--------------------")
  39. return
  40. }
  41. v := reflect.ValueOf(i)
  42. PrintValue(v)
  43. }
  44. func PrintValue(v reflect.Value) {
  45. fmt.Println("--------------------")
  46. // ----- 通用方法 -----
  47. fmt.Println("String :", v.String()) // 反射值的字符串形式
  48. fmt.Println("Type :", v.Type()) // 反射值的类型
  49. fmt.Println("Kind :", v.Kind()) // 反射值的类别
  50. fmt.Println("CanAddr :", v.CanAddr()) // 是否可以获取地址
  51. fmt.Println("CanSet :", v.CanSet()) // 是否可以修改
  52. if v.CanAddr() {
  53. fmt.Println("Addr :", v.Addr()) // 获取地址
  54. fmt.Println("UnsafeAddr :", v.UnsafeAddr()) // 获取自由地址
  55. }
  56. // 获取方法数量
  57. fmt.Println("NumMethod :", v.NumMethod())
  58. if v.NumMethod() > 0 {
  59. // 遍历方法
  60. i := 0
  61. for ; i < v.NumMethod()-1; i++ {
  62. fmt.Printf(" ┣ %v\n", v.Method(i).String())
  63. }
  64. fmt.Printf(" ┗ %v\n", v.Method(i).String())
  65. // 通过名称获取方法
  66. fmt.Println("MethodByName :", v.MethodByName("String").String())
  67. }
  68. switch v.Kind() {
  69. // 结构体:
  70. case reflect.Struct:
  71. fmt.Println("=== 结构体 ===")
  72. // 获取字段个数
  73. fmt.Println("NumField :", v.NumField())
  74. if v.NumField() > 0 {
  75. var i int
  76. // 遍历结构体字段
  77. for i = 0; i < v.NumField()-1; i++ {
  78. field := v.Field(i) // 获取结构体字段
  79. fmt.Printf(" ├ %-8v %v\n", field.Type(), field.String())
  80. }
  81. field := v.Field(i) // 获取结构体字段
  82. fmt.Printf(" └ %-8v %v\n", field.Type(), field.String())
  83. // 通过名称查找字段
  84. if v := v.FieldByName("ptr"); v.IsValid() {
  85. fmt.Println("FieldByName(ptr) :", v.Type().Name())
  86. }
  87. // 通过函数查找字段
  88. v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
  89. if v.IsValid() {
  90. fmt.Println("FieldByNameFunc :", v.Type().Name())
  91. }
  92. }
  93. }
  94. }

输出结果:

  1. --------------------
  2. String : <main.ss Value>
  3. Type : main.ss
  4. Kind : struct
  5. CanAddr : false
  6. CanSet : false
  7. NumMethod : 1
  8. <func(int) string Value>
  9. MethodByName : <invalid Value>
  10. === 结构体 ===
  11. NumField : 4
  12. int <int Value>
  13. string 结构体
  14. bool <bool Value>
  15. float64 <float64 Value>
  16. --------------------
  17. String : <*main.ss Value>
  18. Type : *main.ss
  19. Kind : ptr
  20. CanAddr : false
  21. CanSet : false
  22. NumMethod : 2
  23. <func(int) string Value>
  24. <func(int) string Value>
  25. MethodByName : <invalid Value>
  26. --------------------
  27. String : <func(int) string Value>
  28. Type : func(int) string
  29. Kind : func
  30. CanAddr : false
  31. CanSet : false
  32. NumMethod : 0
  33. --------------------
  34. String : <func(int) string Value>
  35. Type : func(int) string
  36. Kind : func
  37. CanAddr : false
  38. CanSet : false
  39. NumMethod : 0

structValue, &structValue的反射结果是不一样的,指针对象在这里有两个方法,而值对象只有一个方法,这是因为Method2()方法是指针方法,在值对象中是不能被反射到的。

我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用:Method(n).Call(XXXX),具体传参怎么传再说。

『GoLang』反射的更多相关文章

  1. 『Golang』Martini框架入门

    本文介绍golang中的优秀web开发框架martini! 序 Martini框架是使用Go语言作为开发语言的一个强力的快速构建模块化web应用与服务的开发框架.Martini是一个专门用来处理Web ...

  2. 『Golang』MongoDB在Golang中的使用(mgo包)

    有关在Golang中使用mho进行MongoDB操作的最简单的例子.

  3. 『Golang』在Golang中使用json

    由于要开发一个小型的web应用,而web应用大部分都会使用json作为数据传输的格式,所以有了这篇文章. 包引用 import ( "encoding/json" "gi ...

  4. 『Golang』—— 标准库之 os

    Golang 的 os 库基本承袭 Unix 下 C 语言的用法 path 库: func Base(path string) string //取文件名,不含目录部分 func Dir(path s ...

  5. 『GoLang』fmt包的使用

    目录 1. fmt 包初识 2. 格式化 verb 应用 2.1 通用 2.2 布尔值 2.3 整数 2.4 浮点数与复数 2.5 字符串和 []byte 2.6 指针 2.7 其他 flag 2.8 ...

  6. 『GoLang』string及其相关操作

    目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...

  7. 『GoLang』结构体与方法

    结构体 结构体类型 Go 通过结构体的形式支持用户自定义类型,或者叫定制类型. Go 语言结构体是实现自定义类型的一种重要数据类型. 结构体是复合类型(composite types),它由一系列属性 ...

  8. 『Golang』跨平台TUI(基于文字的用户界面)库Terbox-Go文档翻译

    原文 package termbox import "github.com/nsf/termbox-go" termbox-go 是一个用于创建跨平台TUI(基于文本的用户界面)的 ...

  9. 『Golang』Go简介以及环境搭建

    简介 go语言是由Google进行维护的一个编程语言,发布自2009年.其以良好的编程风格.优秀的并发机制被广大的技术人员所接受. 使用go语言开发的优秀的产品: Docker gocode lime ...

随机推荐

  1. C# 调用DOS 命令

    class NetWorkDeviceInfo { public static string GetDeviceInfo() { System.Diagnostics.Process p = new  ...

  2. VS 添加自定义--代码块 实现一秒创建方法

    创建一个方法 你是不是不可避免需要敲以下至少6行代码 现在教你一个方法 实现一秒创建完整方法 首先按照代码块规则创建代码块文件 代码块意义,是什么? 请参考: https://docs.microso ...

  3. 定位API的原理

    参考:0Day 安全 所有的win_32程序都会加载ntdll.dll和kerner32.dll这两个最基础的动态链接库.如果想要在win_32平台下定位kernel32.dll中的API地址 1,首 ...

  4. mysql基础操作(三):数据约束

    首先创建一个数据库 create database homework default character set utf8; use homework; 1.1 默认值约束(default) -- 数 ...

  5. urllib3中学到的LRU算法

    介绍 urllib3._collections.py::RecentlyUserContainer类,是一个线程安全的Dict类容器,用来维护一定数量(maxsize)的Key-Value映射, 当数 ...

  6. Spring Boot +Vue 项目实战笔记(三):数据库的引入

    这一篇的主要内容是引入数据库并实现通过数据库验证用户名与密码. 一.引入数据库 之前说过数据库的采用是 MySQL,算是比较主流的选择,从性能和体量等方面都比较优秀,当然也有一些弊端,但数据库不是我们 ...

  7. ros-kinetic install error: sudo rosdep init ImportError: No module named 'rosdep2'

    refer to: https://blog.csdn.net/yueyueniaolzp/article/details/85070093 方法一 将Ubuntu默认python版本设置为2.7 方 ...

  8. .NetCore3.1获取文件并重新命名以及大批量更新及写入数据

    using Microsoft.AspNetCore.Mvc; using MySql.Data.MySqlClient; using System; using System.Collections ...

  9. Qt5获取系统文件图标,文件路径

    获取系统图标: QFileIconProvider icon_provider; QIcon icon = icon_provider.icon(QFileIconProvider::Folder); ...

  10. noip模拟21

    开题发现这场考过,定睛一看,发现是省选前最后一场,没改过呀--但是还是讲武德的赛时没提交 A. Median 神奇之处在于 \(1e7\) 个质数居然能线性筛出来~ 那么 \(S2\) 可以直接筛出来 ...