官方对于反射有简洁的介绍:
- 反射提供一种让程序检查自身结构的能力
- 反射是困惑的源泉。 要深入了解反射,就需要先理解类型和接口概念。
类型
静态类型
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 = ttyr的类型始终都是io.Readerinterface类型,无论其存储的是什么值。但是r是如何体现它接受的File结构体类型的变量的呢?这是因为r保存了一个(value,type)对来表示其所存储值的信息,value就是r所持有元素的值,type即为所持有元素的底层类型。
使用类型断言,可以将r转为另一个类型结构体变量,比如io.Writer,w = 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.Valuereflect.Type
使用反射要牢记,反射对象可以与interface相互转换,并且如果获得的反射对象可修改,value的值必须是可设置的。