官方对于反射有简洁的介绍:

  1. 反射提供一种让程序检查自身结构的能力
  2. 反射是困惑的源泉。 要深入了解反射,就需要先理解类型和接口概念。

类型

静态类型

Go是静态类型语言,每个变量都有一个静态类型并且在编译时就确定了。 不过对于下面的声明:

type Myint int
var i int
var j Myint

虽然i和j的底层类型是相同的,但是二者仍然拥有不同的静态类型。没有类型转换的时候不可以互相赋值。

特殊静态类型interface

interface类型是一种特殊的类型,它代表方法集合,可以存放任何实现了其方法的值。

最常用来举例的就是io包中的两个接口类型:

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface{
	Write(p []byte) (n int, err error)
}

任何类型,只要实现了其中d的Read()方法就被认为是实现了Reader接口,只要实现了Write()方法,就被认为是实现了Writer接口。

接口类型的变量可以存储任何实现该接口的值。

特殊的interface类型

最特殊的interface类型是空interface类型,即interface{},空interface类型的方法集合为空,也就是说所有类型都可以认为是实现了该接口。

空interface最重要的作用是可以接存放所有值

interface类型是如何表示的

对于下面的代码:

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
	return nil, err
}
r = tty

r的类型始终都是io.Readerinterface类型,无论其存储的是什么值。但是r是如何体现它接受的File结构体类型的变量的呢?这是因为r保存了一个(value,type)对来表示其所存储值的信息,value就是r所持有元素的值,type即为所持有元素的底层类型

使用类型断言,可以将r转为另一个类型结构体变量,比如io.Writerw = r.(io.Writer),意思就是如果r所持有的元素同样实现了io.Writer接口,那么就把值传递给w。

反射三定律

反射就是检查interface的这个(value,type)对的,具体一点就是说GO提供一组方法提取interface的value,提供另一组方法提取interface的type。

反射包提供了获取(value,type)的两个反射对象:

  • reflect.Type类型对象
  • reflect.Value类型对象

官方提供了反射的三条定律。

反射第一定律:反射可以将interface类型变量转换成反射对象

下面的示例就是通过放射获取一个变量的值和类型:

package main
import (
	"fmt"
	"reflect"
)
func main() {
	var x float64 = 3.4
	t := reflect.TypeOf(x)
	fmt.Println("type:", t) // type: float64
 
	v := reflect.ValueOf(x)
	fmt.Println("value:", v) // value: 3.4
}

反射是针对interface变量的,其中TypeOf()ValueOf()接受的参数都是interface{}类型的,也就是说x值是被转为了interface传入的

反射第二定律:反射可以将反射对象还原成interface对象

这里也就是说,反射对象与interface对象是可以互相转换的。

package main
import (
	"fmt"
	"reflect"
)
func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x)
	var y float64 = v.Interface().(float64)
	fmt.Println("value:", y)
}

对象x转换为反射对象v,v又通过Interface()接口转换成interface对象,interface对象通过.(float64)类型断言获取float64类型的值。

反射第三定律:反射对象若要可修改,value值必须是可设置的

通过反射可以将interface类型变量转换成反射对象,再使用反射对象设置其持有的值。 下面的例子中,通过反射对象v设置新值,会出现panic错误。

package main
import (
	"reflect"
)
func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x)
	v.SetFloat(7.1)
}

错误的原因就是v是不可修改的

反射对象是否可修改取决于所存储的值,上面传入reflect.ValueOf()函数的是x的值而非x本身,即通过修改其值是无法影响x的,也即是无效的修改,所以golang会报错。所以构建反射对象v的时候需要传入x的地址,那么此时v代表的就是指针地址了,reflect.Value提供了Elem()方法,可以获得指针指向的value。

package main
import (
	"reflect"
	"fmt"
)
func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(&x)
	v.Elem().SetFloat(7.1)
	fmt.Println("x:", v.Elem().Interface()) // x: 7.1
}

对于变量的内存全部在栈上的,获取反射对象的时候如果不传入地址肯定会拷贝,并且value是无法修改的;对于内存在堆上的,可以通过传入的对象进行修改就可以不用传地址。

总结

Go语言是静态类型语言,所有的变量都有类型,并且类型之间不进行转换是无法互相赋值的。而interface则是一个特殊的静态类型,空interface更是一种可以接受任何类型的值的特殊类型。

interface是保存了(value,type)对,即底层的值value和其类型type,只要type是实现了interface的接口,那么它就可以接受值。

反射就是提供了获取value,type的一组方法,对应反射对象:

  • reflect.Value
  • reflect.Type

使用反射要牢记,反射对象可以与interface相互转换,并且如果获得的反射对象可修改,value的值必须是可设置的。