go进阶之标准库reflect(反射)

一、 go中变量的内在机制

  • 在介绍反射之前,我们首先要了解的是Go语言中的变量的内在机制,因为反射就是基于这种机制实现的

  • Go语言中的变量是分为两部分的

    • 类型信息:预先定义好的元信息
    • 值信息:程序运行过程中通过赋值和初始化等操作而可动态变化的

二、反射

1. 反射是把双刃剑

  • 注意:反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个
    • 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后
    • 大量使用反射的代码通常难以理解
    • 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级

2. 反射的简介

  • 反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息

  • 支持反射的语言可以在程序自上而下进行编译时将已编译的变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们

  • 在python中,反射是利用字符串在一个指定的对象中找到与该字符串相同名字的属性或方法。而GO语言中的反射是为了将给定的对象,利用反射的方法,求得其数据类型和值的信息,并且可以修改其值(如某个未知的结构体或者空接口中包含的内容,就可以用反射一步步取出其中的值,也能修改某个值)

  • Go程序在运行期使用 reflect 库访问程序的反射信息

三、reflect

  • 在Go语言的反射机制中,接口值都由是 一个具体类型具体类型的值 两部分组成的(在上一篇接口的博客中的类型断言中提到过)。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由 reflect.Typereflect.Value 两部分组成,
  • reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象TypeValue

1. reflect.TypeOf

  • 在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var a float32 = 3.14
	v1 := reflect.TypeOf(a)
	fmt.Printf("type:%v\n", v1)  // type:float32

	var b int64 = 100
	v2 := reflect.TypeOf(b)
	fmt.Printf("type:%v\n", v2)  // type:int64
}

(1)reflect.Type的 name 和 kind

  • 在反射中关于类型还划分为两种:类型名(name)种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,name 为 自定义类型的名字,而种类(Kind)就是指该自定义类型的底层的类型
  • 在反射中,一般基础数据类型变量, name 和 kind 是相同的(如 string、int、byte、bool 等类型的变量),而当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类
  • Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回
package main

import (
	"fmt"
	"reflect"
)

type myInt int64

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("name:%v kind:%v\n", t.Name(), t.Kind())
}

func main() {
	var a *float32  // 指针
	var b myInt     // 自定义类型
	var c rune      // 类型别名
	var c1 byte     // 类型别名
	var f string    // 字符串
	var g int       // 整型
	var h bool      // 布尔型
	reflectType(a)  // name: kind:ptr
	reflectType(b)  // name:myInt kind:int64
	reflectType(c)  // name:int32 kind:int32
	reflectType(c1) // name:uint8 kind:uint8
	reflectType(f)  // name:string kind:string
	reflectType(g)  // name:int kind:int
	reflectType(h)  // name:bool kind:bool

	type person struct {
		name string
		age  int
	}
	type book struct{ title string }
	var d = person{
		name: "沙河小王子",
		age:  18,
	}
	var e = book{title: "《跟小王子学Go语言》"}
	reflectType(d)  // type:person kind:struct
	reflectType(e)  // type:book kind:struct

	type tt interface {
	}
	
    if l == nil {
		fmt.Println(55555)  // 55555
	}
    
	var k tt
	t := reflect.TypeOf(k)
	fmt.Println(t)  // <nil>   注意:reflect.TypeOf() 的参数是一个 nil 的接口类型时,则该函数返回 nil

}

(2)kind 的能返回的类型如下

  • reflect包中定义的Kind类型如下
type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

2. reflect.ValueOf

  • reflect.ValueOf()返回的是reflect.Value类型,其中包含了原始值的值信息。reflect.Value 与原始值的类型之间可以进行类型转换

  • reflect.Value类型提供的获取原始值的方法如下:

    • 方法 说明
      Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
      Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
      Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
      Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
      Bool() bool 将值以 bool 类型返回
      Bytes() []bytes 将值以字节数组 []bytes 类型返回
      String() string 将值以字符串类型返回

(1)反射取值

package main

import (
	"fmt"
	"reflect"
)

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}
func main() {
	var a float32 = 3.14
	var b int64 = 100
	v := reflect.ValueOf(a)
	k := v.Kind()
	fmt.Println("55555", v, k)

	reflectValue(a) // type is float32, value is 3.140000
	reflectValue(b) // type is int64, value is 100
    
	// 将int类型的 10 转换为reflect.Value类型的 10
	c := reflect.ValueOf(10)
	d := c.Kind()
	fmt.Printf("type c :%T,%v,%v\n", c, c, d) // type c :reflect.Value,10,int
}

/*
55555 3.14 float32
type is float32, value is 3.140000
type is int64, value is 100       
type c :reflect.Value,10,int
*/

(2)反射改值

  • 想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中不用 * 取值,而是使用专有的Elem()方法来获取指针对应的值,然后通过Elem().SetInt()、Elem().SetFloat()、Elem().SetBytes()、Elem().SetString()、Elem().SetLen()、Elem().SetCap()、Elem().SetMapIndex()等方法改值
package main

import (
	"fmt"
	"reflect"
)

func reflectSetValue1(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Kind() == reflect.Int64 {
		fmt.Printf("11111: %v\n", v)
		v.SetInt(200) //修改的是副本,且reflect包会引发panic
	} else {
		fmt.Println("22222:", v, v.Kind())
	}
}
func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用 Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		fmt.Println("33333", v.Elem())
		v.Elem().SetInt(200)  // 通过SetInt方法修改
	}
}
func main() {
	var a int64 = 100
	// reflectSetValue1(a) // 直接传值然后直接 SetInt 会报错,panic: reflect: reflect.Value.SetInt using unaddressable value
	fmt.Println(a) // 100

	reflectSetValue2(&a)  // 传入指针
	fmt.Println(a) // 200
}

3. isNil() 和 isValid()

  • IsNil()用来判断 reflect.ValueOf() 的返回的值是否为 nil 。传入reflect.ValueOf()的参数必须是 通道、函数、接口、map、指针、切片中的类型之一;否则 IsNil 函数会导致 panic

  • IsValid()用来判断 reflect.ValueOf() 的返回值是否持有指定的值。reflect.ValueOf() 的返回值是 nil 时会返回 false,返回值为 nil 时能调用的方法除了 IsValid、String、Kind之外,别的方法都会导致panic

isNil的函数定义:
func (v Value) IsNil() bool

isValid的函数定义:
func (v Value) IsValid() bool


- 示例

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// *int类型空指针
	var a *int
	fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
	// nil值
	fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
	// 实例化一个匿名结构体
	b := struct{}{}
	// 尝试从结构体中查找"abc"字段
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
	// 尝试从结构体中查找"abc"方法
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
	// map
	c := map[string]int{}
	// 尝试从map中查找一个不存在的键
	fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}

/*
var a *int IsNil: true
nil IsValid: false       
不存在的结构体成员: false
不存在的结构体方法: false
map中不存在的键: false
*/

四、结构体的反射

  • 任意值通过reflect.TypeOf()获得反射对象即reflect.Type信息后,如果它的类型是结构体,可以通过 reflect.TypeNumField()Field() 方法获得结构体成员的详细信息

  • reflect.Type 中与获取结构体成员相关的的方法如下表所示

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据传入的匹配函数匹配需要的字段。
NumMethod() int 返回该类型的方法集中方法的数目
Method(int) Method 返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool) 根据方法名返回该类型方法集中的方法

1. StructField类型

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

  • StructField 的定义如下:

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

2. 结构体反射示例

  • 遍历字段示例
  • 遍历方法示例

(1)遍历结构体内的字段

  • 当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息
package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	stu1 := student{
		Name:  "小王子",
		Score: 90,
	}

	t := reflect.TypeOf(stu1)
	fmt.Println(t.Name(), t.Kind())  // student struct
	// 通过for循环遍历结构体的所有字段信息
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("111--name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
	}

	// 通过字段名获取指定结构体字段信息
	if scoreField, ok := t.FieldByName("Score"); ok {
		fmt.Printf("222--name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
	}
}

/*
student struct
111--name:Name index:[0] type:string json tag:name
111--name:Score index:[1] type:int json tag:score 
222--name:Score index:[1] type:int json tag:score
*/

(2)遍历结构体内的方法

package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s student) Study() string {
	msg := "好好学习,天天向上。"
	fmt.Println(msg)
	return msg
}

func (s student) Sleep() string {
	msg := "好好睡觉,快快长大。"
	fmt.Println(msg)
	return msg
}

func printMethod(x interface{}) {
	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

	fmt.Println("111--", t.NumMethod())
	fmt.Println("222--", v.NumMethod())
	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("333--method name:%s\n", t.Method(i).Name)
		fmt.Printf("444--method:%s\n", methodType)
		// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		var args = []reflect.Value{}
		v.Method(i).Call(args)  // 调用当前这个方法
        fmt.Println("555")
	}
}

func main() {
	stu1 := student{
		Name:  "小王子",
		Score: 90,
	}
	printMethod(stu1)
}

/*
111-- 2
222-- 2                  
333--method name:Sleep   
444--method:func() string
好好睡觉,快快长大。     
555                      
333--method name:Study   
444--method:func() string
好好学习,天天向上。     
555 
*/
posted @ 2024-03-08 17:35  BigSun丶  阅读(265)  评论(0)    收藏  举报