go语言反射(译)

528 阅读5分钟

这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

原文链接:www.geeksforgeeks.org/reflection-…

反射是程序运行期间的内省和分析自己结构的一种能力。在go语言中,反射主要是用类型types实现。反射包提供了这种需求所有需要的API和方法。反射经常被称为元编程。为了更好的理解反射,我们首先粗浅地理解一下空接口:

“An interface that specifies zero methods is known as the empty interface.”

这句话的意思是:一个接口没有声明方法,就是空接口。

当我们想声明一个带有未知的参数和数据类型时,空接口是非常有用的。库方法例如Println,Printf使用空接口作为参数。空方法拥有确定的隐藏属性来提供能力。在下面这种方法中,数据是抽象的。

image.png

  • 例子一
// Understanding Empty Interfaces in Golang
package main

import (
	"fmt"
)


func observe(i interface{}) {
	
	// using the format specifier
	// %T to check type in interface
	fmt.Printf("The type passed is: %T\n", i)
	
	// using the format specifier %#v
	// to check value in interface
	fmt.Printf("The value passed is: %#v \n", i)
	fmt.Println("-------------------------------------")
}

func main() {
	var value float64 = 15
	value2 := "GeeksForGeeks"
	observe(value)
	observe(value2)
}
  • 输出
The type passed is: float64
The value passed is: 15 
-------------------------------------
The type passed is: string
The value passed is: "GeeksForGeeks" 
-------------------------------------

从这里我们可以清晰得看出来,一个空接口可以接收任意的参数并且适配它的值类型和数据类型。这里包括但不限制结构体和指向结构体的指针。

为什么需要反射?

经常来说,传递给这些空接口类型的数据并不是原始类型。例如它们可能是结构体。我们需要在不需要知道它们当前的类型或者值时,使用这些数据来运行程序。因此为了在同样的结构下运行不同的操作,例如编译当前的数据来查询数据库或者给数据库创建约束条件,我们需要知道它当前的类型和字段数量。这些问题都可以通过运行期间使用反射来解决。

反射包

go反射的根基围绕着Values,TypesKinds。这些被定义在包里,分别是类型reflect.Value,reflect.Type 和 reflect.Kind,可以使用这些方法来获取。

  1. reflect.ValueOf(x interface{}).
  2. reflect.TypeOf(x interface{}).
  3. Type.Kind().
TypeKind
Type代表了go中的类型。例如用户在go中定义/自定义的类型,用户声明的名字就会被保存为Type,例如mypack.UserKind代表了Type中的类型。例如用户定义/自定义的类型中,Type中的数据类型就是Type,例如string/struct
Type可以使用reflect.TypeOf(x interface{})获取Kind可以使用.Kind()获取

反射包提供了我们很多其他方法:

  1. NumField:这个方法返回了一个结构体定义的字段的数量。如果传递的参数并不是Kind reflect.Struct就会发生panic。
  2. Field:这个方法允许我们访问结构体中的每一个字段,使用一个索引变量。

在下面这些例子,我们会发现一个结构体/其他自定义类型中Kind和Type的不同。除此之外,我们将使用反射包中的方法来获取并打印出结构体中的字段以及自定义类型的值。

  • 例子二
// Example program to show difference between
// Type and Kind and to demonstrate use of
// Methods provided by Go reflect Package
package main

import (
	"fmt"
	"reflect"
)

type details struct {
	fname string
	lname string
	age	 int
	balance float64
}

type myType string

func showDetails(i, j interface{}) {
	t1 := reflect.TypeOf(i)
	k1 := t1.Kind()
	t2 := reflect.TypeOf(j)
	k2 := t2.Kind()
	fmt.Println("Type of first interface:", t1)
	fmt.Println("Kind of first interface:", k1)
	fmt.Println("Type of second interface:", t2)
	fmt.Println("Kind of second interface:", k2)
	
	fmt.Println("The values in the first argument are :")
	if reflect.ValueOf(i).Kind() == reflect.Struct {
		value := reflect.ValueOf(i)
		numberOfFields := value.NumField()
		for i := 0; i < numberOfFields; i++ {
			fmt.Printf("%d.Type:%T || Value:%#v\n",
			(i + 1), value.Field(i), value.Field(i))
			
			fmt.Println("Kind is ", value.Field(i).Kind())
		}
	}
	value := reflect.ValueOf(j)
	fmt.Printf("The Value passed in "+
	"second parameter is %#v", value)
}

func main() {
	iD := myType("12345678")
	person := details{
		fname: "Go",
		lname: "Geek",
		age:	 32,
		balance: 33000.203,
	}
	showDetails(person, iD)
}

输出:

Type of first interface: main.details
Kind of first interface: struct
Type of second interface: main.myType
Kind of second interface: string
The values in the first argument are :

1.Type:reflect.Value || Value:"Go"
Kind is  string
2.Type:reflect.Value || Value:"Geek"
Kind is  string
3.Type:reflect.Value || Value:32
Kind is  int
4.Type:reflect.Value || Value:33000.203
Kind is  float64
The Value passed in second parameter is "12345678"

在上面的例子中,我们在函数showDetails()中传递了两个参数,函数接收了空接口作为参数。参数是:

  1. person,是一个结构体
  2. iD,是一个string 我们已经使用了方法reflect.TypeOf(i interface{})Type.Kind()方法来获取这些字段,我们可以看到输出的不同。
  • 结构体person是Type main.details,Kind是reflect.Struct
  • 变量iD是Type main.myTypeKind string

因此Type和Kind的区别变得清晰,根据它们的定义。这是go语言中的基本面。此外,我们已经使用了反射包中的方法reflect.ValueOf(), .NumField()  和  .Field() 来获取空接口中的值,结构体中的字段数量,还有分别获取每个字段的值。这可能归功于在运行期间反射的使用,这允许我们来决定参数的Type和Kind。

注意:NumField() 和 *.Field() 这两个方法只能用于结构体。如果元素不是结构体会发生panic。结构指定成%T不能用来打印Kind。如果我们传递i.Kind() 在一个Printf声明一个%T,将会打印出reflect.Kind,这本质上是go中的所有Kinds。

值得注意的是,value.Field(i)  中的类型是 reflect.Value ,这是Type,并不是Kind。Kind是下一行。因此我们看到go语言中的反射的重要性和功能。明白了运行期间变量的类型允许我们写很多通用的代码。因此反射是go语言中一个不可或缺的基本面。