Skip to main content

reflection

提到反射(reflection),我第一下想到的就是Spring框架,里面用到了大量的反射。

反射是在java出现后迅速流行起来的一种概念。通过反射,你可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作

在java中,你可以读取配置并根据类型名称创建对应的类型,这是一种常见的编程手法。Java中很多重要的框架和技术(比如spring/IOC、Hibernate,Struts)等都严重依赖于反射功能。使用javaEE时很多人都觉得很麻烦,比如说需要配置大量xml格式的配置程序,但这毕竟不是反射的错,反而更加说明了反射所带来的高可配置性。

大多数现代高级语言都以各种形式支持反射功能,除了一切以性能为上的C++。 Golang的反射实现了反射的大部分功能,但没有像java语言那样内置类型工厂,故而无法做到像java那样通过类型字符串创建对象实例。

反射是吧双刃剑,功能强大但代码可读性不理想。若非必要,不推荐使用反射

golang中的反射

golang中的反射和其他语言有比较大的不同。首先我们要理解2个基本的概念--Type和Value,他们也是golang包中reflect空间里最重要的2哥类型。我们先看一下下面的定义:

type MyHeader struct{
Name string
}

func (r MyHeader) Read(p []byte)(n int,err error){
//实现自己的Read方法
}

因为MyHeader类型实现了io.Reader接口的所有方法(其实也就是一个Read()函数),所以MyHeader实现了io.Reader。我们可以通过如下方式来进行MyHeader的实例化和赋值:

var reader io.Reader
reader = &MyHeader{"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,这个方法返回该类型的具体信息:Unit,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类型,如果你在注意到这个函数之前就直接设置了值,很有可能会收到一些看起来像一场的错误处理信息。

可能很多人会质疑为什么要有这么个奇怪的函数,可以设置所有的域不是很好吗?这里先解释一下这个函数存在的原因。

我们知道golang中所有的类型都是值类型,即这些变量在传递给函数的时候将发生一次复制。基于这个原则,我们再次看一下下面的语句:

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的值还是原来的呢?为了解决这个问题,golang引入了可设置属性这个概念(Setability)。

如果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("setablility of p:",p.CanSet())

v := p.Elem()
fmt.Println("setablility of v",v.CanSet())

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

3. 判断是否实现了某接口

package main

import (
"fmt"
"reflect"
)

type People interface {
Walk()
Speak()
}

type Stu struct {
}

func (s Stu) Walk() {

}

func (s Stu) Speak() {

}

func main() {
stuTp := reflect.TypeOf(Stu{})
peoTp := reflect.TypeOf((*People)(nil)).Elem()
fmt.Println(stuTp.Implements(peoTp)) //true
}

对结构的反射操作

1. 获取字段

之前讨论都是简单的类型反射,现在来讨论一下结构的反射操作。下面的示例掩饰了如何获取一个结构中所有成员的值:

type T struct{
A int
B string
}

t :=T{203,"mh203"}
s :=reflect.ValueOf(&t).Elem()

typeOfT := s.Type()

for i :=0;i<s.NumberField();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

可以看出,对于结构的反射操作并没有根本上的不同,只是用了Field()方法来按索引获取对应的成员。当然在试图修改成员的值时,也需要注意可赋值属性

2. 获取方法并调用

3. 获取tag

4. 获取嵌套字段

总结

其实 Type Value interface{} 是可以相互转化的