go反射的基本介绍

go反射

反射可以在程序运行的时候检查代码的元信息,动态的修改代码行为。
官网地址
开始之前,得先说说go中的类型。

类型和接口

  1. 类型
    我们知道,go中的类型是静态类型,变量声明的时候需要指定变量类型(go中也支持类型推断,但这不影响本质)。
    比如
type UserType int
var admin UserType
var visitor int

adminvisitor本质类型是一样的,但他俩的的静态类型不一样,如果不强转的话,两者是不能直接转换的。

  1. 接口
    go中的接口代表了一组方法的集合,接口可以用来存储实际的实现了该接口的值,接口变量不能存储接口变量
    接口变量存储变量的时候,其实存储了一对数据(实际的值,实际的类型信息),例子如下:
// 定义接口变量
var r io.Reader
// 创建实际对象
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
// 接口变量存储实际的值
r = tty

r变量包含了一对数据,(tty,*os.File),通过接口变量可以访问该接口里面的方法,要是将接口变量变为实际类型,需要通过断言。在我们的例子中,代码如下:

f := r.(*os.File)

*os.File实现了除io.Reader中的其他接口的方法,需要通过断言来做,不能直接通过io.Reader接口来做,因为io.Reader只提供它的方法的访问。代码如下:

var w io.Writer
w = r.(io.Writer)

按照我们上面的所说的,上面的断言是可以成功的,因为r里面存储的是一对(实际的值,实际的类型信息),因为*os.File确实实现了io.Writer接口。

  1. interface{}
    它代表一组空方法的集合,可以存储任意值,因为每种类型都有0个或更多的方法。从上面的例子来对比。*os.File实现了io.Reader接口,
    io.Reader接口中只有一个方法,*os.File中有除io.Reader之外的其他方法,io.Reader是可以接收*os.File变量的。所以类比到
    interface{},他就可以接收任意值。
    它不是动态类型,而是静态类型,接口类型的变量总是有相同的静态类型,即使在程序运行的时候,接口类型变量实际存储的值可能会发送变化,但这个值
    总是满足接口的要求,其实就是多态。代码如下:
func TestAction(t *testing.T) {
    insert := InsertAction{}
    update := UpdateAction{}
    CallAction(insert)
    CallAction(update)
}
func CallAction(action Action)  {
    action.DoAction()
}
type Action interface {
    DoAction()
}
type InsertAction struct {}

func (i InsertAction) DoAction() {
    fmt.Println("InsertAction")
}

type UpdateAction struct {}

func (i UpdateAction) DoAction() {
    fmt.Println("UpdateAction")
}
func (i UpdateAction) DoAction1() {
fmt.Println("UpdateAction")
}

同样的道理,如果把上面例子中的CallAction中的入参变为interface{}的话,也没有问题,但interface{}中没有方法,所以,没有方法可以调。除非利用断言。

反射

有了上面基础,反射只是检查存储在接口变量中的那一对数据(实际的值,实际的类型信息)。同样在反射中对应两种类型的变量

  1. Type
  2. Value

分别获取实际的值,实际的类型信息。对应两种方法来获取,从Value对象是可以拿到Type对象的。

  1. reflect.TypeOf。
  2. reflect.ValueOf。

要注意,在TypeOf,ValueOf的入参都是interface{}(空接口),反射就通过这个接口变量来检查实际存储的值。

反射包提供了很多的方法用来操作。官方地址如下:

https://pkg.go.dev/reflect

在这里只是说明一下简单的使用,具体的还得去翻官网API,在最后会有一个demo来使用一下反射。下面从几个方面来介绍一下反射。

  1. 从实际类型获取反射相关对象。
  2. 从反射对象获取实际的值。
  3. 通过反射对象来修改实际的值。
  4. 结构体相关的一些方法。

从实际值获取反射相关对象

上面已经说了Type和Value,通过TypeOf,ValueOf来获取对应的值,它俩分别对应接口变量中存储的一对数据(实际值,实际类型),其中一个方法比较有意思。kind()方法在Value和Type对象中都有,Kind方法返回Kind类型,Kind类型是int的别名,它的作用是返回变量实际的类型(int,Float64等),怎么理解实际类型呢?

type MyType int

这里的int就是实际类型,MyType就是静态类型。

在这里插入图片描述

也就是Kind是不能区分静态类型是实际类型的,但Type可以。

从反射对象获取实际的值

可以通过Value对象来访问变量值,Value提供了一些方法可以方法到,Type对象不行,要记住Type获取的是实际值的类型,类型是不能访问到实际的值的。代码如下:

    var i MyKind = 1
	ofValue := reflect.ValueOf(i)
	// 对int类型的访问,如果在int类型上调用String,或者Bool这种不属于它类型的就会报错,但interface是可以的。
    // 这里要注意,go是按照最高位数返回,返回类型为int64
	println(ofValue.Int())
	println(ofValue.String())
	println(ofValue.Bool())
    // 返回类型为float64
	println(ofValue.Float())
   // 返回类型为uint64
	println(ofValue.Uint())
	println(ofValue.Interface())

因为interface{}可以接受任何值,那么上面的例子中还可以这么写

func main() {
	var i MyKind = 1
	ofValue := reflect.ValueOf(i)
    // 这里返回是实际的值,int()表示实际的类型
	println(ofValue.Int())
	// 返回interface利用断言来做,但是得注意,这里断言的是静态类型,如果换为int,就会报错
	println(ofValue.Interface().(MyKind)) 
}

实际的参数不变,只是接受参数的变量类型不变,既然可以利用反射从接口变量中获取实际的值,当然也可以反着来。interface()方法实际就是将实际的值和实际类型打包放在一块,返回了回来,在断言的时候只要保证类型一致就可以直接转换。总之interface()方法是ValueOf的逆方法,它的返回值也是Interface{}

通过反射对象修改实际值,这个值必须是可以设置的

代码如下:

var x float64 = 3.4
v := reflect.ValueOf(x)
// 会报错,报错为 panic: reflect.Value.SetFloat using unaddressable value
v.SetFloat(7.1)

Value对象中有CanSet()方法表示是否可以设置值,不能设置值任然设置,就会如上面的例子一样。上面报错的原因是在于v不可设置值。

ar x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet()) //结果为true

可设置值比较像可寻址,意思就是,通过变量能找到原始的值,这个属性是由反射对象所持有得原始数据项来决定的。go参数传递都是值传递,所以当做如下的操作时:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

其实就是将x 复制了一份给ValueOf,所以,这里反射对象操作的是复制的那份数据,而不是x本身,所以,当SetFloat的时候,就会产生歧义,作为使用者来说,想改的是x,结果x没有变。这就有问题了,可设置性就是为了处理这个问题的。如果想修改要怎么办?

传递指针。虽然指针也是copy了一份,但是两个指针指向的数据是同一个。这就消除了这种歧义。

在这里插入图片描述

有两个注意点:

  1. 创建反射对象的时候,传递的是x的指针。
  2. 指针对象对应的反射对象是不可设置值的。

第一个上面已经说清楚了,第二个,都已经是指针对象了为啥还不能设置值。

指针获取的反射对象是不能设置值的,因为想要设置值的不是它,他也是复制的,想要设置的是指针所指向的变量。那么?怎么获取呢?

// 获取指针对象所指向的变量
v := p.Elem()
// true
fmt.Println("settability of v:", v.CanSet())

现在v是一个可以设置值的对象,v就是上面例子中的x,所以现在可以通过v来设置x,因为它代表x。代码如下:

func main() {
	var x float64 = 3.4
	// 传递的是指针
	p := reflect.ValueOf(&x)
	//
	fmt.Println("type of p:", p.Type())
	fmt.Println("settability of p:", p.CanSet())
	
	// 这也是一种获取指针对象所指向的实际值的方法,这里换位.Elem也是可以的。
	indirect := reflect.Indirect(p)
	if indirect.CanSet() { 
		indirect.SetFloat(7.1)
	}
	println(x)
}

需要注意:利用反射修改值的时候需要传递指针

结构体相关方法

反射可以获取结构体里面的字段,tag,方法等等。下面的例子中,获取字段,获取结构体,调用方法。

type Kinds struct {
	Name string
	Age int
	price float64
}

func (k *Kinds) Names()  {
	println(k.Name,"llalla")
}

func main() {
	kinds := Kinds{
		Name:  "12",
		Age:   1,
		Price: 2,
	}
	// 传递的是指针,先获取指针对象所对应的反射对象,在通过Elem获取真正的对象
	of := reflect.ValueOf(&kinds).Elem()
	// 或者真正对象的类型
	t := of.Type()
	// 获取字段的数量
	for i := 0; i < t.NumField(); i++ {
		// 获取字段,这里是直接在value上获取的
		field := of.Field(i)
		fmt.Printf("%d: %s %s = %v\n", i,
			 // 字段的名称,注意,这里是在Type对象上获取的,要记住Type保存类型信息,Value爆粗实际的值类型
			t.Field(i).Name, 
			// 在value上获取
			field.Type(),
			// 这里最重要,只有value对象持有真正的数据
			field.Interface())
	}
	
	// 获取指针对象所对应的反射对象
	valueOf := reflect.ValueOf(&kinds)
	t2 := valueOf.Type()
	// 这里要注意,方法是在 kinds的指针对象上面的,不是在Kind上,所以,如果通过 reflect.ValueOf(&kinds).Elem().Type().NumMethod() 结果为0
	for i := 0; i < t2.NumMethod(); i++ {
		method := t2.Method(i)
		value := method.Func
		// 调用方法,函数和方法是不一样的,第一个参数为作用域,也就是接受对象,如果是函数的话是不需要的。
		value.Call([]reflect.Value{valueOf})
	}
}

代码上写了注释,要注意以下几点:

  1. 方法的接受对象要分清(指针还是非指针)

  2. 操作反射对象时要将Type和Value的作用分清,以及自己在操作的时候操作的是什么,比如获取字段的值就不能从Type对象中获取。

  3. 在go中可以看到Python的影子,在go中类型是很灵活的。函数,方法也是有value的。通过反射来调用函数,代码如下:

    func add(a,b int) int  {
    	return a+b
    }
    func main() {
        // 获取add方法对应的反射对象
    	valueOf := reflect.ValueOf(add)
        // 组装参数调用,要注意,函数和方法调用的区别,方法有接收者的。Call的第一个参数是接收者
    	values := []reflect.Value{
    		reflect.ValueOf(10),
    		reflect.ValueOf(12),
    	}
    	res := valueOf.Call(values)
    	for _, item := range res {
    		println(item.Int())
    	}
    }
    

博客参考laws-of-reflection
例子单独写一章


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值