Golang反射包应用

236 阅读3分钟

你好,Gophers!今天我们将对Go中一个叫做reflect的包进行更深入的研究。它属于中级到高级实现的范畴。在本教程中,我们将看到它是什么以及如何使用它。如果想更深入地了解,你也可以看看《在**反射中实现反射》和《Go中的类型转换**》。

什么是反射?

反射是一个程序在运行时分析其变量和值并确定其类型的能力。虽然反射可以用来打印出已知/定义的数据类型(如int、float、String等)的类型和值,但在处理任意的或抽象的组件结构时,它更有用。我们会在自己的实现中看到这一点,所以不用担心。

请看Go的开发者之一Rob Pike的《反射法则》。

空接口

正如我们在 Go中的反射和类型转换中 简单讨论的那样,空接口是Go中一个特殊的数据类型,这样如果我们存储任何值,例如x:=6.626,那么即使它被称为 "接口 "而不是 "int "或 "float",x的类型信息也不会丢失。我们也可以用x创建另一个变量,然后它的类型就不是 "interface "而是 "float64"。

func main() {
	var x interface{}
	x = 6.626
	fmt.Printf("x: type = %T, value = %v\n", x, x)
	goo := x
	fmt.Printf("goo: type = %T, value = %v\n", goo, goo)
        x = int(2020)
        fmt.Printf("x: type = %T, value = %v\n", x, x)
}

因此,如果我们有一个空接口类型的变量,那么我们可以给这个变量分配不同的值,因为它似乎包含两个属性/字段。类型和值。所以当我们把x的值重新赋给int时,我们只是更新了这两个字段的值。

现在在 Go的反射和类型转换中 ,我们考虑了两种自定义类型--ID和Person,要求包含特定类型的变量可以通过类型断言完成。

//x.(<type>) is the format for type assertion

然而,使用类型断言的缺点是,我们必须确定该变量是否属于该类型。否则,你的程序可能会崩溃。一个变通的办法是,在断言的同时要求一个bool值。

if v, ok := x.(ID); ok

或者我们可以使用switchcase做一个类型转换。

不幸的是,它对我们不知道的类型不起作用。所以它对自定义数据类型和自定义函数输出不起作用。

反映类型/值

所以,让我们从导入reflect包开始。

package main

import (
	"fmt"
	"reflect"
)

然后考虑TypeofValueOf 方法。

func main() {
	var x interface{}
	x = 6.626

	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

reflect.TypeOf(x)返回一个reflect.Type类型的值,这是一个结构,隐藏了Go如何识别类型的一些细节(数据抽象),并向它添加了一些我们可以操作的方法。

类似地,reflect.ValueOf(x)返回一个reflect.Value类型的值。

从本质上讲,这两个函数在我们的空接口中寻找并提取类型和值的信息,这些信息作为一个字段并不真正存在,但却像一个字段一样。

所以现在我们可以把之前的代码改成这样,它的工作原理是一样的。

	fmt.Printf("x: type = %v, value = %v\n", t, v)
	goo := x
	fmt.Printf("goo: type = %T, value = %v\n", goo, goo)

所以,我们可以看一下这个反射的一个简单方法--Kind()。

func main() {
	var x interface{}
	x = &struct{ name string }{}

	t0 := reflect.TypeOf(x)
	v0 := reflect.ValueOf(x)
	fmt.Printf("x: type = %v, value = %v\n", t0, v0)

	x = new(string)

	t1 := reflect.TypeOf(x)
	v1 := reflect.ValueOf(x)
	fmt.Printf("x: type = %v, value = %v\n", t1, v1)

	fmt.Printf("t0: type = %v, kind = %v\n", t0, t0.Kind())
	fmt.Printf("t1: type = %v, kind = %v\n", t1, t1.Kind())
}

在这里,我们定义了一个指向结构的指针和一个用于比较的字符串。

所以Kind方法就像一个超级类型,也就是说,无论指针指向什么,Kind都会输出ptr。还有什么具有相同的类型?

a0 := [5]int{}
a1 := [10]int{}
//are the same kind (arrays), but very different types

//Similarly
b0 := make([]int{})
b1 := make([]float{})
//are also the same kind (slices), but very different types