[TOC]
### 反射是什么
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
### 反射的三大定律
* 反射可以将“接口类型变量”转换为“反射类型对象”;
* 反射可以将“反射类型对象”转换为“接口类型变量”;
* 如果要修改“反射类型对象”,其值必须是“可写的”(settable)。
### 用法
#### reflect.TypeOf()
```
func TypeOf(i interface{}) Type
```
在反射中关于类型还划分为两种:`类型(Type)`和`种类(Kind)`
`类型(Type)`:就是自己定义的类型
`种类(Kind)`:指底层的类型
~~~
type newInt int
func main() {
var testNum newInt = 10
typeOfTestNum := reflect.TypeOf(testNum)
fmt.Println(typeOfTestNum) //main.newInt
//获取类型
fmt.Println(typeOfTestNum.Name()) //newInt
//获取种类,即底层数据类型
fmt.Println(typeOfTestNum.Kind()) //int
fmt.Println(reflect.ValueOf(testNum)) //10
}
~~~
Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的`.Name()`都是返回`空`。
#### reflect.ValueOf()
```
func ValueOf(i interface{}) Value
```
使用reflect.ValueOf() 获取的值的类型是`reflect.Value`类型,其中包含了原始值的值信息。
可以进行转化
| 方法 | 说明 |
| --- | --- |
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
| Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
| Bool() bool | 将值以 bool 类型返回 |
| Bytes() \[\]bytes | 将值以字节数组 \[\]bytes 类型返回 |
| String() string | 将值以字符串类型返回 |
~~~go
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}
func main() {
var a float32 = 3.14
var b int64 = 100
reflectValue(a) // type is float32, value is 3.140000
reflectValue(b) // type is int64, value is 100
// 将int类型的原始值转换为reflect.Value类型
c := reflect.ValueOf(10)
fmt.Printf("type c :%T\n", c) // type c :reflect.Value
}
~~~
### isNil()和isValid()
#### isNil()
常被用于判断指针是否为空
~~~go
func (v Value) IsNil() bool
~~~
`IsNil()`报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
#### isValid()
常被用于判定返回值是否有效
~~~go
func (v Value) IsValid() bool
~~~
`IsValid()`返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。
~~~
func main() {
var testNum int = 10
ptrOfTestNum := &testNum
valueOfTestNum := reflect.ValueOf(ptrOfTestNum)
fmt.Println(valueOfTestNum.IsNil()) // false
fmt.Println(valueOfTestNum.IsValid()) //true
}
~~~
#### 修改反射值
使用反射进行值属性获取或修改值前,**一定要确保操作对象是可寻址的**。对于修改值得操作,还要**确保操作对象是可被修改的**。Go 语言提供了相应的方法帮助我们进行判断,分别是 CanAddr() 和 CanSet();
~~~
func main() {
var testNum int = 10
ptrOfTestNum := &testNum
valueOfPtrTestNum := reflect.ValueOf(ptrOfTestNum)
fmt.Println(valueOfPtrTestNum.Elem().CanAddr())
fmt.Println(valueOfPtrTestNum.Elem().CanSet())
valueOfPtrTestNum.Elem().SetInt(20)
}
~~~
**Elem() 函数的作用是返回指针指向的数据**
注意:使用 Elem() 函数时,若作用于非指针或接口时,将引发宕机。作用于空指针时,将返回 nil。
~~~
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag.ro()
}
return x
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
~~~
### 结构体反射
`reflect.Type`中与获取结构体成员相关的的方法如下表所示。
| 方法 | 说明 |
| --- | --- |
| Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
| NumField() int | 返回结构体成员字段数量。 |
| FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
| FieldByIndex(index \[\]int) StructField | 多层成员访问时,根据 \[\]int 提供的每个结构体的字段索引,返回字段的信息。 |
| FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
| NumMethod() int | 返回该类型的方法集中方法的数目 |
| Method(int) Method | 返回该类型方法集中的第i个方法 |
| MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 |
~~~
rt := reflect.TypeOf(&Teacher{})
fmt.Println(rt.NumMethod(), rt.Method(0).Name)
for i := 0; i < rt.NumField(); i++ {
s := rt.Field(i)
fmt.Println(s, s.Name, s.Type, s.Tag)//获取所有的tag,获取单个tag,Tag.Get(“json”)
}
rt1 := reflect.ValueOf(T1{Name: "hahha", Age: 55})
for i := 0; i < rt1.NumField(); i++ {
//s1 := rt.Field(i)
s := rt1.Field(i)
//fmt.Println(s1.Name, s)
fmt.Println(s) //hahha 55
}
~~~
### 使用反射调用函数
~~~
func addCalc(num1 int, num2 int) int {
return num1 + num2
}
func main() {
ret := reflect.TypeOf(addCalc)
fmt.Println(ret) //func(int, int) int
ret1 := reflect.ValueOf(addCalc)
fmt.Println(ret1) //0xe39da0
//用call调用,并传参,参数类型是reflect.Value
res := ret1.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf(10)})
fmt.Println(res[0])
}
~~~
### 使用反射创建实例
通过反射创建实例,通常**用于创建一个与已知变量同类型的变量**。如此创建的**变量类型只有在程序运行时才会被确定,更加灵活多变**。
举例来说,现有一个变量 num,它的类型是自定义的 myInt 类型。我们若想创建与其相同类型的变量,方法如下:
~~~go
type myInt int
func main() {
var num myInt = 100
typeOfNum := reflect.TypeOf(num)
anotherNum := reflect.New(typeOfNum)
anotherNum.Elem().SetInt(300)
fmt.Println(num)
fmt.Println(anotherNum.Type(), anotherNum.Type().Kind())
fmt.Println(anotherNum.Elem().Int())
}
~~~
使用反射创建变量,核心在于**reflect.New() 函数。该函数接收 reflect.Type 类型参数,返回 reflect.Value 类型值**。该值是一个指针,本例中的 anotherNum 类型实际上是 \*main.myInt,从种类上讲是 ptr。
运行这段代码,控制台输出如下:
> 100
> *main.myInt ptr
> 300
- Go准备工作
- 依赖管理
- Go基础
- 1、变量和常量
- 2、基本数据类型
- 3、运算符
- 4、流程控制
- 5、数组
- 数组声明和初始化
- 遍历
- 数组是值类型
- 6、切片
- 定义
- slice其他内容
- 7、map
- 8、函数
- 函数基础
- 函数进阶
- 9、指针
- 10、结构体
- 类型别名和自定义类型
- 结构体
- 11、接口
- 12、反射
- 13、并发
- 14、网络编程
- 15、单元测试
- Go常用库/包
- Context
- time
- strings/strconv
- file
- http
- Go常用第三方包
- Go优化
- Go问题排查
- Go框架
- 基础知识点的思考
- 面试题
- 八股文
- 操作系统
- 整理一份资料
- interface
- array
- slice
- map
- MUTEX
- RWMUTEX
- Channel
- waitGroup
- context
- reflect
- gc
- GMP和CSP
- Select
- Docker
- 基本命令
- dockerfile
- docker-compose
- rpc和grpc
- consul和etcd
- ETCD
- consul
- gin
- 一些小点
- 树
- K8s
- ES
- pprof
- mycat
- nginx
- 整理后的面试题
- 基础
- Map
- Chan
- GC
- GMP
- 并发
- 内存
- 算法
- docker