[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