Go语言反射(reflection)简述
Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,但是在编译时并不知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
反射(reflection)是在 Java 出现后迅速流行起来的一种概念。通过反射,你可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。
在 Java 中,你可以读取配置并根据类型名称创建对应的类型,这是一种常见的编程手法。Java 中的很多重要框架和技术(比如 Spring/IoC、Hibernate、Struts)等都严重依赖于反射功能。虽然,使用 Java EE 时很多人都觉得很麻烦,比如需要配置大量 XML 格式的配置程序,但这毕竟不是反射的错,反而更加说明了反射所带来的高可配置性。
大多数现代的高级语言都以各种形式支持反射功能,除了一切以性能为上的 C++ 语言。Go语言的反射实现了反射的大部分功能,但没有像 Java 语言那样内置类型工厂,故而无法做到像 Java 那样通过类型字符串创建对象实例。
反射是把双刃剑,功能强大但代码可读性并不理想。若非必要,并不推荐使用反射,下面我们将介绍反射功能在 Go语言中的具体体现以及反射的基本使用方法。
对所有接口进行反射,都可以得到一个包含 Type 和 Value 的信息结构。比如我们对上例的 reader 进行反射,也将得到一个 Type 和 Value,Type 为 io.Reader,Value 为 MyReader{"a.txt"}。顾名思义,Type 主要表达的是被反射的这个变量本身的类型信息,而 Value 则为该变量实例本身的信息。
可能很多人会置疑为什么要有这么个奇怪的函数,可以设置所有的域不是很好吗?这里先解释一下这个函数存在的原因。
我们在之前的学习中提到过 Go语言中所有的类型都是值类型,即这些变量在传递给函数的时候将发生一次复制。基于这个原则,我们再次看一下下面的语句:
假如 v 允许调用 Set(),那么我们也可以想象出,被修改的将是这个 x 的副本,而不是 x 本身。如果允许这样的行为,那么执行结果将会非常困惑。调用明明成功了,为什么 x 的值还是原来的呢?为了解决这个问题 Go语言,引入了可设属性这个概念(Settability)。如果 CanSet() 返回 false,表示你不应该调用 Set() 和 SetXxx() 方法,否则会收到这样的错误:
反射(reflection)是在 Java 出现后迅速流行起来的一种概念。通过反射,你可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。
在 Java 中,你可以读取配置并根据类型名称创建对应的类型,这是一种常见的编程手法。Java 中的很多重要框架和技术(比如 Spring/IoC、Hibernate、Struts)等都严重依赖于反射功能。虽然,使用 Java EE 时很多人都觉得很麻烦,比如需要配置大量 XML 格式的配置程序,但这毕竟不是反射的错,反而更加说明了反射所带来的高可配置性。
大多数现代的高级语言都以各种形式支持反射功能,除了一切以性能为上的 C++ 语言。Go语言的反射实现了反射的大部分功能,但没有像 Java 语言那样内置类型工厂,故而无法做到像 Java 那样通过类型字符串创建对象实例。
反射是把双刃剑,功能强大但代码可读性并不理想。若非必要,并不推荐使用反射,下面我们将介绍反射功能在 Go语言中的具体体现以及反射的基本使用方法。
基本概念
Go语言中的反射与其他语言有比较大的不同。首先我们要理解两个基本概念 Type 和 Value,它们也是 Go语言包中 reflect 空间里最重要的两个类型。我们先看一下下面的定义:type MyReader struct { Name string } func (r MyReader)Read(p []byte) (n int, err error) { // 实现自己的Read方法 }因为 MyReader 类型实现了 io.Reader 接口的所有方法(其实就是一个 Read() 函数),所以 MyReader 实现了接口 io.Reader。我们可以按如下方式来进行 MyReader 的实例化和赋值:
var reader io.Reader reader = &MyReader{"a.txt"}现在我们可以再来解释一下什么是 Type,什么是 Value。
对所有接口进行反射,都可以得到一个包含 Type 和 Value 的信息结构。比如我们对上例的 reader 进行反射,也将得到一个 Type 和 Value,Type 为 io.Reader,Value 为 MyReader{"a.txt"}。顾名思义,Type 主要表达的是被反射的这个变量本身的类型信息,而 Value 则为该变量实例本身的信息。
基本用法
通过使用 Type 和 Value,我们可以对一个类型进行各项灵活的操作。接下来我们分别演示反射的几种最基本用途。1) 获取类型信息
为了理解反射的功能,我们先来看看下面代码所示的这个小程序。package main import ( "fmt" "reflect" ) func main() { var x float64 = 3.4 fmt.Println("type:", reflect.TypeOf(x)) }运行上述代码,输出结果如下所示:
type: float64
Type 和 Value 都包含了大量的方法,其中第一个有用的方法应该是 Kind,这个方法返回该类型的具体信息:Uint、Float64 等。Value 类型还包含了一系列类型方法,比如 Int(),用于返回对应的值。查看以下示例:var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())结果为:
type: float64
kind is float64: true
value: 3.4
2) 获取值类型
类型 Type 中有一个成员函数 CanSet(),其返回值为 bool 类型。如果你在注意到这个函数之前就直接设置了值,很有可能会收到一些看起来像异常的错误处理消息。可能很多人会置疑为什么要有这么个奇怪的函数,可以设置所有的域不是很好吗?这里先解释一下这个函数存在的原因。
我们在之前的学习中提到过 Go语言中所有的类型都是值类型,即这些变量在传递给函数的时候将发生一次复制。基于这个原则,我们再次看一下下面的语句:
var x float64 = 3.4 v := reflect.ValueOf(x) v.Set(4.1)最后一条语句试图修改 v 的内容。是否可以成功地将 x 的值改为 4.1 呢?先要理清 v 和 x 的关系。在调用 ValueOf() 的地方,需要注意到 x 将会产生一个副本,因此 ValueOf() 内部对 x 的操作其实都是对着 x 的一个副本。
假如 v 允许调用 Set(),那么我们也可以想象出,被修改的将是这个 x 的副本,而不是 x 本身。如果允许这样的行为,那么执行结果将会非常困惑。调用明明成功了,为什么 x 的值还是原来的呢?为了解决这个问题 Go语言,引入了可设属性这个概念(Settability)。如果 CanSet() 返回 false,表示你不应该调用 Set() 和 SetXxx() 方法,否则会收到这样的错误:
panic: reflect.Value.SetFloat using unaddressable value
现在我们知道,有些场景下不能使用反射修改值,那么到底什么情况下可以修改的呢?其实这还是跟传值的道理类似。我们知道,直接传递一个 float 到函数时,函数不能对外部的这个 float 变量有任何影响,要想有影响的话,可以传入该 float 变量的指针。下面的示例小幅修改了之前的例子,成功地用反射的方式修改了变量 x 的值:var x float64 = 3.4 p := reflect.ValueOf(&x) // 注意:得到X的地址 fmt.Println("type of p:", p.Type()) fmt.Println("settability of p:" , p.CanSet()) v := p.Elem() fmt.Println("settability of v:" , v.CanSet()) v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x)
对结构的反射操作
之前讨论的都是简单类型的反射操作,现在我们讨论一下结构的反射操作。下面的示例演示了如何获取一个结构中所有成员的值:type T struct { A int B string } t := T{203, "mh203"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) }以上例子的输出为:
0: A int = 203
1: B string = mh203
所有教程
- socket
- Python基础教程
- C#教程
- MySQL函数
- MySQL
- C语言入门
- C语言专题
- C语言编译器
- C语言编程实例
- GCC编译器
- 数据结构
- C语言项目案例
- C++教程
- OpenCV
- Qt教程
- Unity 3D教程
- UE4
- STL
- Redis
- Android教程
- JavaScript
- PHP
- Mybatis
- Spring Cloud
- Maven
- vi命令
- Spring Boot
- Spring MVC
- Hibernate
- Linux
- Linux命令
- Shell脚本
- Java教程
- 设计模式
- Spring
- Servlet
- Struts2
- Java Swing
- JSP教程
- CSS教程
- TensorFlow
- 区块链
- Go语言教程
- Docker
- 编程笔记
- 资源下载
- 关于我们
- 汇编语言
- 大数据
- 云计算
- VIP视频