Go语言3小时光速入门07——*反射

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

每种语言的反射模型都不同,Go语言的反射机制就是在运行时动态的调用对象的方法和属性,其中reflect包就是反射相关的,只要包含这个包就可以使用。像是go语言的gRPC等都是通过反射实现的。

*Go语言中类型设计的一些原则

在go中变量包括类型type和值value两部分。(这也是为什么nil != nil)

type包括static type和concrete type。简单来说static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型

而类型断言能否成功,取决于变量的concrete type,而不是static type。因此,假设一个reader变量如果它的concrete type也实现了write函数的话,它也可以被类型断言为writer(回想前面的面向对象)。

Go的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Go的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。

每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:(value,type)即变量的真实值和实际类型。在interface中包含两个指针分别指向类型(concrete type)和实际的值。

Reflect

go语言中反射由reflect包实现(感兴趣的话可以自行去查看相应的源码也有很详细的说明和示例,光type完整代码就3千多行就不贴在这里了...),它定义了两个重要类型:Type和Value。

在reflect中主要提供了两个方法让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf() 和 reflect.TypeOf()

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}

ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0


// 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 {...}

TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil


reflect.TypeOf()获取pair中的type

reflect.ValueOf()获取pair中的value

如:
reflect_test.go:

/*
将“接口类型变量”转换为“反射类型对象”,反射类型指的是reflect.Type和reflect.Value这两种
 */
func TestReflect(t *testing.T){
	var num int = 123
	//reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
	fmt.Println("type: ", reflect.TypeOf(num))
	//reflect.ValueOf:直接给到了我们想要的具体的值,如123这个具体数值,或者类似&{值1, 值2, 值3} 这样的结构体struct的值
	fmt.Println("value: ", reflect.ValueOf(num))
}

reflect.ValueOf(interface)返回一个relfect.Value变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。

如果我们知道转换的原有的类型是什么,可以通过value.Interface().(类型)进行转换

realValue := value.Interface().(已知的类型)
  1. 先获取interface的reflect.Type,然后通过NumField进行遍历
  2. 再通过reflect.Type的Field获取其Field
  3. 最后通过Field的Interface()得到对应的value

如:
reflect_test.go:

func TestReflectKnowType(t *testing.T){
	var num int = 123
	//主要在go中的指针类型和类型
	refValue := reflect.ValueOf(&num)
	//这里是 *int 而不是int。同样如果上面是num那么这里就是int
	//如果类型错了会直接panic
	value := refValue.Interface().(*int)
	t.Log(value)
}

当然知道类型的情况并不多见,所以如果我们不知道类型的话就需要遍历探测其Filed。通过reflect.TypeOf()返回一个type。通过getType.NumField()获取field数量,然后通过type.Field(第几个)来进行遍历。

  1. 先获取interface的reflect.Type,然后通过NumMethod进行遍历
  2. 再分别通过reflect.Type的Method获取对应的真实的方法(函数)
  3. 最后对结果取其Name和Type得知具体的方法名
  4. 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
  5. struct 或者 struct 的嵌套都是一样的判断处理方式

如:

type User struct {
	Id   int
	Name string
	Sex  int
}

func (u User) ReflectCall() {
	fmt.Println("233333")
}

func TestFiled(t *testing.T) {
	user := User{1, "张三", 1}
	DoFiledAndMethod(user)
}

// 通过接口来获取任意参数,然后一一揭晓
func DoFiledAndMethod(input interface{}) {

	getType := reflect.TypeOf(input)
	fmt.Println("Type : ", getType.Name())

	getValue := reflect.ValueOf(input)
	fmt.Println("Fields : ", getValue)

	// 获取方法字段
	// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
	// 2. 再通过reflect.Type的Field获取其Field
	// 3. 最后通过Field的Interface()得到对应的value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	// 获取方法
	// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
	for i := 0; i < getType.NumMethod(); i++ {
		m := getType.Method(i)
		fmt.Printf("%s: %v\n", m.Name, m.Type)
	}
}

reflect.Value设置实际变量的值

reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。

我们可以通过通过pointer.Elem()去获取所指向的Value,然后对newValue.Set(xxx)进行修改设置值

newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改

reflect_test.go:

func TestReflectSetValue(t *testing.T){
	num  := 123
	fmt.Println("num : ", num)

	// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
	refValue := reflect.ValueOf(&num)
	//refValue = reflect.ValueOf(num)
	// 如果非指针,这里直接panic,
	newValue := refValue.Elem()

	fmt.Println("refValue type :", newValue.Type())
	fmt.Println("refValue settability :", newValue.CanSet())

	// 重新赋值,除了SetInt外当然还要SetString,Set...之类的
	newValue.SetInt(321)
	fmt.Println("num :", num)
}

注意:Value.Elem()的Value一定要是指针否则会直接panic,设值之前通过CanSet()检查。

reflect.ValueOf函数的调用

我们可以通过Value.MethodByName(函数名)来获取一个函数(返回一个函数的Value),然后通过call调用。

如:

type User struct {
	Id   int
	Name string
	Sex  int
}

func (u User) PrintName() {
	fmt.Println("233333")
	fmt.Println("name: ",u.Name)
}

func (u User) SetName(name string) {
	u.Name = name
	fmt.Println("name: ",u.Name)
}

func TestFiled(t *testing.T) {
	user := User{1, "张三", 1}
	DoFiledAndMethod(user)
}

// 通过接口来获取任意参数,然后一一揭晓
func DoFiledAndMethod(input interface{}) {

	getType := reflect.TypeOf(input)
	fmt.Println("Type : ", getType.Name())

	getValue := reflect.ValueOf(input)
	fmt.Println("Fields : ", getValue)
	//如果方法名错误会panic
	method := getValue.MethodByName("PrintName")
	args := make([]reflect.Value, 0)
	method.Call(args)
	//按照方法的参数顺序构建对应的参数
	method = getValue.MethodByName("SetName")
	args = []reflect.Value{reflect.ValueOf("李四")}
	method.Call(args)
}

关于反射的坏处基本都知道,破坏结构啊什么的,这里需要额外提醒的是Go中的反射非常慢(如果Java的反射算比较慢的话)。提高 golang 的反射性能
.

*unsafe.Sizeof,Alignof和Offestof

函数unsafe.Sizeof(参数): 报告传递给它的参数在内存中占用的字节长度,这个参数可以是任何类型的表达式,但是它并不会计算表达式。如:unsafe.Sizeof(int(1234567 + 7654321))
Sizeof调用返回一个uintptr类型的常量表达式,所以这个结果可以作为数