深入理解go语言反射机制,看这一篇就够了

golang的反射机制

在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

一、go语言的类型系统以及接口

​ 在go语言中,反射是在类型系统中构建的,类型包括系统内自带的底层类型和自定义类型比方说:

type MyInt int
var i int
var j MyInt

上述代码中i是系统自带类型,j是自定义类型,这代表i和j拥有不同的静态类型但是其底层的系统类型是相同的。

还有一种重要的类型是接口类型,在go语言中接口分为iface和eface分别对应着有方法接口表示一系列方法的集合,还有无方法结构,就是所谓的interface{},只有类型和值的信息不包含方法,这也可以通过查看runtime.iface和runtime.eface查看其中的信息。

type iface struct {
	tab  *itab  // 这里面包含着fun,fun是指向接口具体实现的方法的地址
	data unsafe.Pointer
}
type eface struct { // 空接口只含有类型和值
	_type *_type
	data  unsafe.Pointer
}

让我看一个例子更好的理解接口!

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty
var w io.Writer
w = r.(io.Writer)
var empty interface{}
empty = w

上述代码中,os.OpenFile返回的tty的类型是*os.File这个类型实现了io.Reader和io.Writer两个接口的方法,当用io.Reader接口定义出的值去接tty时候就相当于把*os.File实现的方法取了一次视图(数据库概念),对外只能表现为io.Reader所包含的方法。用r.(io.Writer)可以把接口值重新对外表现为io.Writer。用空接口empty去接w相当于empty只表现了w的类型(*os.File)和值(tty)这两种特性,并不能对外表现出任何方法,因为eface中根本没有指向实现方法的指针。

无论是eface还是iface都包含着两个信息,该接口内包含的值以及其类型

二、reflect.Type && reflect.Value

所有go接口都包含type和value这两个信息,使用reflect包中的Typeof和Valueof可以将这两个信息从接口中剥离出来。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
  fmt.Println("type:", reflect.TypeOf(x))
  fmt.Println("value:", reflect.ValueOf(x).String())
}

Typeof和Valueof方法的入参都是interface{}当传入x时候会做类型转换,将float64类型转换为空接口类型。

查看reflect包源码我们可以看到Typeof和Valueof究竟返回的是什么,Typeof返回的是实现了Type接口的类型值,Valueof返回的是Value结构体,通过使用特定的方法可以获取这个接口所包值的一些信息,也可以修改其值。

type Type interface {
	Method(int) Method
	MethodByName(string) (Method, bool) // 返回方法
	NumMethod() int  // 这个类型有有多少个方法
	Name() string  // 类型名
	PkgPath() string // 定义该类型的包路径
	Kind() Kind  // type Kind uint -> 把基本类型做了一个枚举
	Implements(u Type) bool //  该类型是否实现了接口u的方法
	AssignableTo(u Type) bool
	ConvertibleTo(u Type) bool
	Elem() Type // 返回type所包含的元素的类型,这意味着type必须 Array, Chan, Map, Ptr, Slice其中一种
	Field(i int) StructField // type必须是struct然后返回struct第i个元素的信息
  FieldByIndex(index []int) StructField // 用于返回嵌套字段,例如{1,2,3}就是返回第1个结构体中的第2个结构体中的第3元素
	FieldByName(name string) (StructField, bool) // 用名字范会字段
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	In(i int) Type // 如果type是函数类型返回第i个入参类型
	Out(i int) Type // 如果type是函数类型返回第i个返回值类型
}
type Value struct {
	typ *rtype // *rtype实现了Type接口的方法
	ptr unsafe.Pointer // 指向具体值的指针
}

三、将反射对象转成接口

反射对象Value可以转换成接口

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

v := reflect.ValueOf(0.5)
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y) 

四、 Value包含修改接口具体值的方法,但前提Value必须是可修改类型

什么是可修改类型,比如C语言中的地址int *p,中的p并不是可修改类型,p是指针,这是一个具体值,可修改类型是*p也就是该指针指向的值,所以一个值如果是可以修改值必须包含地址信息。

下面举个例子:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // 不可以修改,因为v中的值是x的拷贝
fmt.Println("settability of v:", v.CanSet()) // false
var x float64 = 3.4
p := reflect.ValueOf(&x) // 这里传入的是一个地址的值
fmt.Println("type of p:", p.Type()) //type of p: *float64,是一个地址的值但不是可修改类型
fmt.Println("settability of p:", p.CanSet()) // 不可修改,指针本身只是一个数,相当于&x这个值的拷贝
fmt.Println("settability of v:", v.Elem().CanSet())//.Elem()在这里相当于*p,这样就可以修改了

[1] [维基百科——反射式编程][https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%B0%84%E5%BC%8F%E7%BC%96%E7%A8%8B]

[2] [go官方文档][https://go.dev/blog/laws-of-reflection]

在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值