Go-reflect

Package reflect实现了运行时反射,允许程序获取任意对象的运行时信息。最常见的用法就是通过调用TypeOf方法获取对象类型;调用ValueOf返回对象运行时数据。反射是程序检查自身结构的一种能力,是一种形式的元编程。

类型和接口(Types and interfaces)

反射构建在类型系统之上。Go是静态类型语言,每个变量都有一个静态类型,在编译阶段确认。

1
2
3
4
5
6
7
8
9
10
11
12
type MyInt int

func main() {
var i int
var j MyInt

fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(j))
}
output:
int
main.MyInt

变量i的类型为int,j的类型为MyInt,虽然ij的底层类型都是int类型,但它们之间如果不进行类型转换则不能进行赋值。
interface{}可以表示任何类型的数据。


Go反射三大法则

1.从interface{}开始进行对象的反射
简单来说反射就是一种从`interface`类型的变量取出里面的`Type`和`value`的一种机制。通过`reflect.TypeOf`和`reflect.ValueOf` 可以分别获取反射的类型和值。
1
2
3
4
5
6
7
8
func main() {
var x float64 = 3.4
r := reflect.TypeOf(x)
fmt.Println("type:", r)
// output: type:float64
fmt.Println(reflect.TypeOf(r))
// output: *reflect.rtype
}

上述代码表明变量x的类型为float64,而且传递给函数TypeOf的参数xfloat64类型,而不是interface类型,所以这跟interface又有什么关系呢?
查看函数TypeOf源码

1
2
3
4
5
6
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}

发现TypeOf的参数类型为interface{},所以参数x会存储在一个空的interface中,然后再从这个interface中恢复类型信息。
func ValueOf(i interface{}) Value也是如此。

2.从反射对象可以获得interface的值

对于一个reflect.Value类型的对象(反射对象),我们可以通过Interfaace方法从中获取interface值。

1
2
3
4
5
6
func main() {
var x float64 = 3.4
r := reflect.ValueOf(x)
fmt.Println(r.Interface())
// output: 3.4
}

1
2
3
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}

reflect.ValueOf的作用是把float64类型的变量转换成reflect.Value类型,而Interface的作用与reflect.ValueOf正好相反。

3.要想修改一个反射对象,反射对象必须是可设置的

执行如下代码

1
2
3
4
5
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
}


所以报错的原因是什么呢?请看如下代码:

1
2
var x float64 = 3.4
v := reflect.ValueOf(x)

因为Go语言的函数调用的参数都是值传递的,所以传递给reflect.ValueOf函数的参数x并不是x本身而是x的一份拷贝。如果v.SetFloat(7.1)可以设置成功,这并不能更新x的值,更新的只是x的拷贝;但是真正的x并没有更新,这样做不但没有用而且会造成很多的疑惑。

要想对原有变量进行修改可以采用如下方式:

1
2
3
4
5
6
7
func main() {
i := 3.4
v := reflect.ValueOf(&i) //获取变量指针
v.Elem().SetFloat(10) //通过Elem()获取指针指向的变量,SetFloat对变量进行更新
fmt.Println(i)
//output :10
}


通过反射对结构体进行修改:

1
2
3
4
5
6
7
8
func main()  {
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()

s.Field(0).SetInt(10)
fmt.Println(s.Field(0))
// output: 10
}


Ref:

1.https://golang.org/pkg/reflect/
2.https://blog.golang.org/laws-of-reflection