什么是反射:

维基百科上的定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

《Go 语言圣经》中是这样定义反射的:Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

不用反射不行吗:

实际上,它的本质是程序在运行期探知对象的类型信息和内存结构,不用反射能行吗?可以的!使用汇编语言,直接和内层打交道,什么信息不能获取?但是,当编程迁移到高级语言上来之后,像java/python/go,就不行了!就只能通过反射来达到此项技能。

为什么要用反射:

1、有时需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了,在程序运行中去动态识别这些参数类型。

2、获取结构体的Tag信息等。

3、显示一个类型的方法集。

4、....

不使用反射的理由:

  1. 与反射相关的代码,经常是难以阅读的。
  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

go语言如何使用反射:

go语言反射里最重要的两个概念是Type和Value。

Type用于获取类型相关的信息(比如Slice的长度,struct的成员,函数的参数个数)

Value用于获取和修改原始数据的值(比如修改slice和map中的元素,修改struct的成员变量)

1、通过TypeOf()得到Type类型:

获取struct成员变量的信息

typeUser := reflect.TypeOf(common.User{}) //需要用struct的Type,不能用指针的Type
fieldNum := typeUser.NumField()           //成员变量的个数
for i := 0; i < fieldNum; i++ {
	field := typeUser.Field(i)
	fmt.Printf("%d %s offset %d anonymous %t type %s exported %t json tag %s\n", i,
		field.Name,            //变量名称
		field.Offset,          //相对于结构体首地址的内存偏移量,string类型会占据16个字节
		field.Anonymous,       //是否为匿名成员
		field.Type,            //数据类型,reflect.Type类型
		field.IsExported(),    //包外是否可见(即是否以大写字母开头)
		field.Tag.Get("json")) //获取成员变量后面``里面定义的tag
}
fmt.Println()

//可以通过FieldByName获取Field
if nameField, ok := typeUser.FieldByName("Name"); ok {
	fmt.Printf("Name is exported %t\n", nameField.IsExported())
}
//也可以根据FieldByIndex获取Field
thirdField := typeUser.FieldByIndex([]int{2}) //参数是个slice,因为有struct嵌套的情况
fmt.Printf("third field name %s\n", thirdField.Name)

获取struct成员方法的信息、获取函数的信息、判断类型是否实现了某接口

# TODO

2、通过ValueOf()得到Value

userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65,
	Height: 1.68,
})
fmt.Println(userPtrValue) //&{7 杰克逊  65 1.68}


//指针Value和非指针Value互相转换
userValue := userPtrValue.Elem()   //Elem() 指针Value转为非指针Value

得到Value对应的原始数据

通过Interface()函数把Value转为interface{},再从interface{}强制类型转换,转为原始数据类型。或者在Value上直接调用Int()、String()等一步到位。

user := userValue.Interface().(common.User)
user2 := userPtrValue.Interface().(*common.User)
fmt.Println(user.Name)
fmt.Println(user2.Name)

通过Value修改原始数据的值

user := common.User{
	Id:     7,
	Name:   "杰克逊",
	Weight: 65.5,
	Height: 1.68,
}

valueUser := reflect.ValueOf(&user) //由于go语言所有函数传的都是值,所以要想修改原来的值就需要传指
 
//由于valueI对应的原始对象是指针,通过Elem()返回指针指向的对象
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通过Name返回类的成员变量