go语言基础-----15-----反射

1 reflect反射

  • 1)反射定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修
    改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修
    改自己的行为。

  • 2)GO 反射的意义:Go 语言的 ORM 库离不开它,Go 语言的 json 序列化库离不开它, fmt包字
    符串格式化离不开它,Go 语言的运行时更是离不开它。

  • 3)反射的目标:

    1. 获取变量的类型信息,例如这个类型的名称、占用字节数、所有的方法列表、所有的内部字段结
      构、它的底层存储类型等等。
    2. 动态的修改变量内部字段值。比如 json 的反序列化,你有的是对象内部字段的名称和相应的值,
      你需要把这些字段的值循环填充到对象相应的字段里。
  • 4)go语言中的反射通过refect包实现,reflect包实现了运行时反射,允许程序操作任意类型的对象。reflect包中的两个关键数据类Type和Value。

func TypeOf(v interface{}) Type // 返回类型 实际是接口
func ValueOf(v interface{}) Value // 返回值 结构体

例如:
var s int = 12
fmt.Println(reflect.TypeOf(s))  // output: int
fmt.Println(reflect.ValueOf(s)) // output: 12

2 reflect反射-利弊

1.1 反射的好处

  1. 为了降低多写代码造成的bug率,做更好的归约和抽象。
  2. 为了灵活、好用、方便,做动态解析、调用和处理。
  3. 为了代码好看、易读、提高开发效率,补足与动态语言之间的一些差别。

1.2 反射的弊端

  1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为
    力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成
    严重的后果。
  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运
    行效率关键位置的代码,尽量避免使用反射特性。

3 reflect反射-Type

3.1 Type

  • 1)Type:Type类型用来表示一个go类型。
  • 2)不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。
  • 3)获取Type对象的方法:func TypeOf(i interface{}) Type。
    例如:
str := "aaa"
res_type := reflect.TypeOf(str)
fmt.Println(res_type) //string

int1 := 1
res_type2 := reflect.TypeOf(int1)
fmt.Println(res_type2) //int

3.2 reflect.Type通用方法

func (t *rtype) String() string // 获取 t 类型的字符串描述,不要通过 String 来判断两种类型是否一致。
func (t *rtype) Name() string // 获取 t 类型在其包中定义的名称,未命名类型则返回空字符串。
func (t *rtype) PkgPath() string // 获取 t 类型所在包的名称,未命名类型则返回空字符串。
func (t *rtype) Kind() reflect.Kind // 获取 t 类型的类别。
func (t *rtype) Size() uintptr // 获取 t 类型的值在分配内存时的大小,功能和 unsafe.SizeOf 一样。
func (t *rtype) Align() int // 获取 t 类型的值在分配内存时的字节对齐值。
func (t *rtype) FieldAlign() int // 获取 t 类型的值作为结构体字段时的字节对齐值。
func (t *rtype) NumMethod() int // 获取 t 类型的方法数量。
func (t *rtype) Method() reflect.Method // 根据索引获取 t 类型的方法,如果方法不存在,则 panic。
// 如果 t 是一个实际的类型,则返回值的 Type 和 Func 字段会列出接收者。
// 如果 t 只是一个接口,则返回值的 Type 不列出接收者,Func 为空值。
func (t *rtype) MethodByName(string) (reflect.Method, bool) // 根据名称获取 t 类型的方法。
func (t *rtype) Implements(u reflect.Type) bool // 判断 t 类型是否实现了 u 接口。
func (t *rtype) ConvertibleTo(u reflect.Type) bool // 判断 t 类型的值可否转换为 u 类型。
func (t *rtype) AssignableTo(u reflect.Type) bool // 判断 t 类型的值可否赋值给 u 类型。
func (t *rtype) Comparable() bool // 判断 t 类型的值可否进行比较操作
//注意对于:数组、切片、映射、通道、指针、接口
func (t *rtype) Elem() reflect.Type // 获取元素类型、获取指针所指对象类型,获取接口的动态类型

例如:

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Age  int
	Name string
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}

func main() {
	stu1 := Student{Name: "aaa", Age: 34}
	inf := new(Skills)
	stu_type := reflect.TypeOf(stu1)

	fmt.Println("类型stu_type:", stu_type) //main.Student
	fmt.Println(stu_type.String())       //main.Student
	fmt.Println(stu_type.Name())         //Student
	fmt.Println(stu_type.PkgPath())      //main
	fmt.Println(stu_type.Kind())         //struct
	fmt.Println(stu_type.Size())         //24

	fmt.Println("===================")

	inf_type := reflect.TypeOf(inf).Elem()                          //  获取指针所指的对象类型
	fmt.Println("\n类型inf_type:", inf_type)                          //main.Skills
	fmt.Println(inf_type.NumMethod())                               //2
	fmt.Println(inf_type.Method(0), "and", inf_type.Method(0).Name) // {reading main func() <invalid Value> 0} adn reading
	fmt.Println(inf_type.MethodByName("reading"))                   //{reading main func() <invalid Value> 0} true

}

在这里插入图片描述

3.3 reflect.Type其他方法

// 数值
func (t *rtype) Bits() int // 获取数值类型的位宽,t 必须是整型、浮点型、复数型
// 数组
func (t *rtype) Len() int // 获取数组的元素个数
// 映射
func (t *rtype) Key() reflect.Type // 获取映射的键类型
// 通道
func (t *rtype) ChanDir() reflect.ChanDir // 获取通道的方向
// 结构体
func (t *rtype) NumField() int // 获取字段数量
func (t *rtype) Field(int) reflect.StructField // 根据索引获取字段
func (t *rtype) FieldByName(string) (reflect.StructField, bool) // 根据名称获取字段
func (t *rtype) FieldByNameFunc(match func(string) bool) (reflect.StructField, bool) // 根据指定的匹配函数 math 获取字段
func (t *rtype) FieldByIndex(index []int) reflect.StructField // 根据索引链获取嵌套字段
// 函数
func (t *rtype) NumIn() int // 获取函数的参数数量
func (t *rtype) In(int) reflect.Type // 根据索引获取函数的参数信息
func (t *rtype) NumOut() int // 获取函数的返回值数量
func (t *rtype) Out(int) reflect.Type // 根据索引获取函数的返回值信息
func (t *rtype) IsVariadic() bool // 判断函数是否具有可变参数。

例如:

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string "json:name"
	Age  int    "json:age"
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "aaa", Age: 34}
	stu_type := reflect.TypeOf(stu1)
	fmt.Println(stu_type.NumField())          //2
	fmt.Println(stu_type.Field(0))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Name")) //{{Age  int  16 [1] false} true
	fmt.Println(stu_type.Field(1))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Age"))  //{{Age  int  16 [1] false} true
}

在这里插入图片描述

3.3 Type结构
rtype 实现了 Type 接口的所有方法。剩下的不同的部分信息各种特殊类型结构体都不一样。可以将 rtype 理解成父类,特殊类型的结构体是子类,会有一些不一样的字段信息。

源码路径:C:\go\src\reflect\type.go

// 基础类型 rtype 实现了 Type 接口
type rtype struct {
size uintptr // 占用字节数
ptrdata uintptr
hash uint32 // 类型的hash值
...
kind uint8 // 元类型
...
}
// 切片类型
type sliceType struct {
rtype
elem *rtype // 元素类型
}
// 结构体类型
type structType struct {
rtype
pkgPath name // 所在包名
fields []structField // 字段列表
}

4 reflect.Value方法

  • 1)reflect.Value.Kind():获取变量类别,返回常量。常量可能的值如下:
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
)

例如:

str := "aaa"
val := reflect.ValueOf(str).Kind()
fmt.Println(val) //string
  • 2)获取值的方法:
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。
func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
  • 3)设置值的方法:
func (v Value) SetInt(x int64) //设置int类型的值
func (v Value) SetUint(x uint64) // 设置无符号整型的值
func (v Value) SetFloat(x float64) // 设置浮点类型的值
func (v Value) SetComplex(x complex128) //设置复数类型的值
func (v Value) SetBool(x bool) //设置布尔类型的值
func (v Value) SetString(x string) //设置字符串类型的值
func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
func (v Value) SetCap(n int) //设置切片的容量
func (v Value) SetBytes(x []byte) //设置字节类型的值
func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加

例如:

// 获取和设置普通类型的值
package main

import (
	"fmt"
	"reflect"
)

func main() {
	// 1. 获取值
	str := "aaa"
	age := 11
	fmt.Println(reflect.ValueOf(str).String()) //获取str的值,结果aaa
	fmt.Println(reflect.ValueOf(age).Int())    //获取age的值,结果11
	str2 := reflect.ValueOf(&str)              //获取Value类型

	// 2. 设置值
	str2.Elem().SetString("bbb")  //设置值
	fmt.Println(str2.Elem(), age) //bbb 11

	// 3. 通过反射获取其Value的地址类型变量,修改该变量原来的变量age也会跟着变
	age2 := reflect.ValueOf(&age) //获取Value类型
	fmt.Println("age2:", age2)    //0xc000012090

	age2.Elem().SetInt(40) //设置值
	fmt.Println("age:", age)
	fmt.Println("reflect.ValueOf(age):", reflect.ValueOf(age))
}

在这里插入图片描述

  • 4)其他方法:
//结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量
func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回
零值(reflect.ValueOf(nil)//通道相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
func (v Value) Close() // 关闭通道
//函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
func (v Value) CallSlice(in []Value) []Value	//调用变参函数老师

例子1:

package main

import (
	"fmt"
	"reflect"
)

type Skills interface {
	reading()
	running()
}

type Student struct {
	Name string "json:name"
	Age  int    "json:age"
}

func (self Student) runing() {
	fmt.Printf("%s is running\n", self.Name)
}
func (self Student) reading() {
	fmt.Printf("%s is reading\n", self.Name)
}
func main() {
	stu1 := Student{Name: "aaa", Age: 34}
	stu_type := reflect.TypeOf(stu1)
	fmt.Println(stu_type.NumField())          //2
	fmt.Println(stu_type.Field(0))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Name")) //{{Age  int  16 [1] false} true
	fmt.Println(stu_type.Field(1))            //{Name  string  0 [0] false}
	fmt.Println(stu_type.FieldByName("Age"))  //{{Age  int  16 [1] false} true
}

结果:
在这里插入图片描述

例子2:

//通过反射调用结构体中的方法,通过reflect.Value.Method(i int).Call()
//或者reflect.Value.MethodByName(name string).Call()实现
package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age  int
}

func (this *Student) SetName(name string) {
	this.Name = name
	fmt.Printf("set name %s\n", this.Name)
}

func (this *Student) SetAge(age int) {
	this.Age = age
	fmt.Printf("set age %d\n", age)
}

func (this *Student) String() string {
	fmt.Printf("this is %s\n", this.Name)
	return this.Name
}

func (this *Student) SetAgeAndName(age int, name string) {
	this.Age = age
	fmt.Printf("set age %d, name:%s\n", age, name)
}

func main() {
	// 1. 通过名称来调用方法
	stu1 := &Student{Name: "aaa", Age: 18}
	val := reflect.ValueOf(stu1)         //获取Value类型,也可以使用reflect.ValueOf(&stu1).Elem()
	val.MethodByName("String").Call(nil) //调用String方法

	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf(18)
	val.MethodByName("SetAge").Call(params) //通过名称调用方法

	params = make([]reflect.Value, 2)
	params[0] = reflect.ValueOf(18)
	params[1] = reflect.ValueOf("ccc")
	val.MethodByName("SetAgeAndName").Call(params)

	params = make([]reflect.Value, 1) // 先重设参数个数为1,否则下面会报错误。

	// 2. 通过索引来调用方法。但可能不太安全。
	params[0] = reflect.ValueOf("bbb")
	// val.Method(1).Call(params) //通过方法索引调用
	val.Method(2).Call(params) //通过方法索引调用	通过索引的方式拿到函数不安全,因为可能每次方法对应的索引不太一样,参数传错可能报错,
	//这点需要注意!!!
	fmt.Println(stu1.Name, stu1.Age)

}

结果:
在这里插入图片描述

5 reflect.Value 结构体

简单看看即可。

type Value struct {
	typ *rtype // 变量的类型结构体
	ptr unsafe.Pointer // 数据指针
	flag uintptr // 标志位
}

6 Go 语言官方的反射三大定律

官方对 Go 语言的反射功能做了一个抽象的描述,总结出了三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.
  • 1)第一个定律的意思是反射将接口变量转换成反射对象 Type 和 Value。
func TypeOf(v interface{}) Type 
func ValueOf(v interface{}) Value
  • 2)第二个定律的意思是反射可以通过反射对象 Value 还原成原先的接口变量,这个指的就是 Value
    结构体提供的 Interface() 方法。
func (v Value) Interface() interface{}
  • 3)第三个定律的功能不是很好理解,它的意思是想用反射功能来修改一个变量的值,前提是这个
    值可以被修改。
    值类型的变量是不可以通过反射来修改,因为在反射之前,传参的时候需要将值变量转换成接口变量,值内容会被浅拷贝,反射对象 Value 指向的数据内存地址不是原变量的内存地址,而是拷贝后的内存地址。这意味着如果值类型变量可以通过反射功能来修改,那么修改操作根本不会影响到原变量的值,那就白白修改了。所以 reflect 包就直接禁止了通过反射来修改值类型的变量。

例如:

package main

import (
	"fmt"
	"reflect"
)

func test1() {
	var s int = 42
	var v = reflect.ValueOf(s)
	v.SetInt(43)
	fmt.Println(s)
}
func test2() {
	var s int = 42
	// 反射指针类型
	var v = reflect.ValueOf(&s)
	// 要拿出指针指向的元素进行修改
	v.Elem().SetInt(43)
	fmt.Println(s)
}
func main() {
	test1()
	test2()
}

结果,会在对应设置值的行数报错,例如这里的test1:
在这里插入图片描述

7 测试使用反射和不使用反射时的计算效率

package main

import (
	"fmt"
	"reflect"
	"time"
)

// 测试一百万次计算,使用与不使用反射的效率
const N = 1000000

func test1() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		// 反射指针类型
		var v = reflect.ValueOf(&s)
		// 要拿出指针指向的元素进行修改
		v.Elem().SetInt(43)
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("反射赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}

func test2() {
	var sum = 0
	t0 := time.Now()

	for i := 0; i < (N); i++ {
		var s int = 42
		s = 43
		sum += s
	}

	elapsed := time.Since(t0)
	fmt.Println("直接赋值 ", "N:", N, "time:", elapsed, ", sum:", sum)
}
func main() {
	//test1()// 反射赋值  N: 1000000 time: 33.0614ms , sum: 43000000
	test2() //直接赋值  N: 1000000 time: 953.8µs , sum: 43000000
}

下面结果看出,在进行计算比较多或者核心代码即要求高效率的话,不要使用反射去计算,否则效率大大降低。

test1()// 反射赋值  N: 1000000 time: 33.0614ms , sum: 43000000
test2() //直接赋值  N: 1000000 time: 953.8µs , sum: 43000000
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值