Go —— 反射

反射

反射是什么?

  • 反射是运行时检查自身结构的机制
  • 反射是困惑的源泉。

反射特性与 interface 紧密相关。

接口

1. 类型

Go是静态类型语言,比如int、float32、[]byte,等等。每个变量都有一个静态类型,而且在编译时就确定了。

考虑如下几类变量声明:

type Myint int

var i int
var j Myint

变量 i 和 j 是相同的类型吗?不是的,二者拥有不同的静态类型,尽管二者的底层类型都是 int ,但在没有类型转换的情况下是不可以相互赋值的。

Go提供了布尔、数值和字符串类型的基础类型,还有一些使用这些基本类型组合的复合类型,比如数组、结构体、指针、切片、map和 channel 等。interface 也可以称为一种复合类型。

2. interface 类型

每个 interface 类型代表一个特性的方法集,方法集中的方法称为接口。比如:

type Animal interface {
    Speak() string
}

Animal 就是一个接口类型,其包含一个 Speak() 方法

1)interface变量

就像任何其他类型一样,我们也可以声明 interface 类型的变量。比如:

var animal Animal

上面的 animal 变量的值为 nil,它没有赋值,它可以存储什么值呢?

2)实现接口

任何类型只要实现了 interface 类型的所有方法,就可以声称该类型实现了这个接口,该类型的变量就可以存储到 interface 变量中。

比如结构体 Cat 实现了Speak() 方法:

type Cat struct {
}

func (c Cat) Speak() string {
    return "Meow"
}

结构体Cat的变量就可以存储到 animal 变量中:

var animal Animal
var cat Cat
animal = cat

interface 变量可以存储到任意实现了该接口类型的变量。

3)复合类型

为什么 interface 变量可以存储任意实现了该类型接口的变量。

因为 interface 类型的变量在存储某个变量时会同时保存变量类型和变量值。

type iface struct {
    tab *itab // 保存变量类型
    data unsafe.Pointer // 变量值位于堆栈的指针
}

Go的反射就是在运行时操作 interface 中的值和类型的特性。

反射定理

interface 类型有一个(value、type)对,而反射就是操纵 interface 的这个(value,type)对的机制。具体一点说就是Go提供一组方式来获取 interface 的value 和 type。

1. reflect包

reflect包中提供了reflect.Typereflect.Value 两个类型,分别代表 interface 中的类型和value。

reflect包中同时提供两个方法来获取 interface 的 value 类型:

func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type

具体关系如下图所示:

在这里插入图片描述

在下面的描述中,我们称reflect.Type 和reflect.Value 为 interface 的反射对象

2. 反射定律

1)第一定律:反射可以将 interface 类型变量转换成反射对象

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	t := reflect.TypeOf(x)
	fmt.Println("type:", t)

	v := reflect.ValueOf(x)
	fmt.Println("value:", v)
}

程序输出如下:

type: float64
value: 3.4

在上面的例子中,好像没有 interface 变量,实则不然,变量 x 在传入 reflect.TypeOf()函数时,实际上做了一次转化,x变量被转换成一个空接口传入,reflect.ValueOf(x)也是如此。该例子展示了反射的一个能力,即可以获取 interface 变量的类型和值,这是反射进一步操纵 interface变量的基础。

2)第二定律:反射可以将反射对象还原成 interface 对象

之所以叫“反射”,是因为反射对象与 interface 对象时可以相互转换的。

func foo() {
	var A interface{}
	A = 100

	v := reflect.ValueOf(A)
	B := v.Interface()
	if A == B {
		fmt.Println("A==B")
	} else {
		fmt.Println("A!=B")
	}
}

在上面的函数中,通过 reflect.ValueOf()获取接口变量A的反射对象,然后又通过反射对象的interface()获取B,结果A和B是相同的。

3)第三定律:反射对象可修改,value值必须是可设置的

通过反射可以将 interface 类型的变量转换成反射对象,可以使用该反射对象设置 interface 变量持有的值。

我们可以通过 reflect.Value 的一系列 SetXXX() 方法来设置反射对象的值。先看一个失败的例子:

package main

import "reflect"

func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x)

	v.SetFloat(7.1) 
}

在上面的代码中,通过反射对象v设置新值,会触发panic。报错如下:

panic: reflect: reflect.Value.SetFloat using unaddressable value

错误原因是v是不可修改的,为什么会如此呢?

反射对象是否可修改取决于其所存储的值。上例中传入 reflect.ValueOf()函数的其实是x的值,而非x本身,即通过v修改其值是无法影响x的,也就是无效的修改,所以会报错。

如果构建v时使用x的地址就可实现修改了,但此时v代表的是指针地址,我们要设置的是指针所指向的内容,即我们想要修改*v。那怎么通过v修改x的值呢?

reflect.Value() 提供了Elem() 方法,可以获得指向 value 的指针。修正后的代码如下:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(&x)

	v.Elem().SetFloat(7.1)
    fmt.Println("x:"x)
}

输出如下:

x: 7.1
  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值