Go 语言入门与进阶:反射基础

1,758 阅读4分钟

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

前文回顾

如果你还没有 Go 语言基础,建议阅读我的 从零学 Go

本系列文章,我将会进一步加深对 Go 语言的讲解,更一步介绍 Go 中的包管理、反射和并发等高级特性。

前面的文章主要介绍了 Go 包依赖管理 GOPATH 和 Go Module 的应用实践。本文将会介绍 Go 反射相关的内容。

反射基础

反射是一项功能强大的工具,它给开发人员提供了在运行时对代码本身进行访问和修改的能力。接下来我们介绍 Go 反射中 Type 和 Value 两个重要的概念。

我们首先定义一些简单的结构体和方法,用于我们后面的实验验证,代码如下所示,主要位于 feature/relection/reflection.go 文件下:

package main

import "fmt"

// 定义一个人的接口
type Person interface {

	// 和人说hello
	SayHello(name string)
	// 跑步
	Run() string
}

type Hero struct {
	Name string
	Age int
	Speed int
}

func (hero *Hero) SayHello(name string)  {
	fmt.Println("Hello " + name, ", I am " + hero.Name)
}

func (hero *Hero) Run() string{
	fmt.Println("I am running at speed " + string(hero.Speed))
	return "Running"
}

上述代码中我们定义了一个 Person 接口,以及定义了 Hero 结构体来实现 Person 接口中的方法,同时 Hero 结构体中还包含 3 个成员字段。

Go 的反射与 Java 等语言有不小的区别,主要通过 Type 和 Value 两个基本概念来阐述。其中 Type 主要用于表示被反射变量的类型信息,而 Value 用于表示被反射变量自身的实例信息。Go 的反射实现主要位于 reflect 包中。

reflect.Type 类型对象

通过 reflect#TypeOf 方法,我们可以轻松地获取一个变量的类型信息 reflect.Type。通过 reflect.Type 类型对象,我们访问到其对应类型的各项类型信息。我们可以创建一个 Hero 结构体,通过 reflect#TypeOf 来查看其对应的类型信息,代码如下所示:

func main()  {
	// 获取实例的反射类型对象
	typeOfHero := reflect.TypeOf(Hero{})
	fmt.Printf("Hero's type is %s, kind is %s", typeOfHero, typeOfHero.Kind())

}

运行结果如下所示:

Hero's type is main.Hero, kind is struct

在 Go 中,存在着 Type (类型)和 Kind (种类) 区别,如上面结果所展示,Hero 的类型是 main.Hero,种类是 struct。Type 是指变量所属的类型,包括系统的原生数据类型如 int、string等和我们通过 type 关键字定义的类型,比如我们定义的 Hero 结构体,这些类型的名称一般就是其类型本身。而 Kind 是指变量类型的所归属的品种,参考 reflect.Kind 中的定义,主要有以下类型:

type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

一般我们通过 type 关键字定义的结构体都属于 Struct,而指针变量的种类统一为 Ptr,比如下面代码:

	fmt.Printf("*Hero's type is %s, kind is %s",reflect.TypeOf(&Hero{}), reflect.TypeOf(&Hero{}).Kind())

上述代码中通过 reflect#TypeOf 获取了 Hero 指针的类型对象,它的输出将会是:

*Hero's type is *main.Hero, kind is ptr

这说明 &Hero{} 的类型是 *main.Helo,归属于种类 ptr。对于指针类型的变量,可以使用 Type#Elem 获取到指针指向变量的真实类型对象,如下例子所示:

	typeOfPtrHero := reflect.TypeOf(&Hero{})
	fmt.Printf("*Hero's type is %s, kind is %s\n",typeOfPtrHero, typeOfPtrHero.Kind())
	typeOfHero = typeOfPtrHero.Elem()
	fmt.Printf(" typeOfPtrHero elem to typeOfHero, Hero's type is %s, kind is %s", typeOfHero, typeOfHero.Kind())

预期输出为:

*Hero's type is *main.Hero, kind is ptr
 typeOfPtrHero elem to typeOfHero, Hero's type is main.Hero, kind is struct

通过 typeOfPtrHero#Elem,我们可以获取到 *main.Helo 指针原类型 main.Hero 的类型对象。

小结

本文主要介绍了 Go 语言的反射基础。通过反射,我们可以拿到丰富的类型信息,比如变量的字段名称、类型信息和结构体信息等,并通过这些类型信息做一些灵活的工作。 Go 的反射实现了反射的大多数功能,获取类型信息需要配合使用标准库中的词法、语法解析器和抽象语法数对源码进行扫描。

下一篇文章将会继续介绍 Go 语言的反射 reflect 相关内容。

阅读最新文章,关注公众号:aoho求索