https://blog.csdn.net/u011957758/article/details/81193806
前言
反射在很多語言中都有其妙用。在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。
本文將記錄筆者對於Golang的反射的筆記。
10s後,以下知識點即將靠近:
1.反射的簡介
2.爲什麼使用反射?
3.反射具體能做什麼
4.反射的一些小點
https://www.jianshu.com/p/53adb1e92710
正文
1.反射的簡介
Golang提供了一種機制,在編譯時不知道類型的情況下,可更新變量、運行時查看值、調用方法以及直接對他們的佈局進行操作的機制,稱爲反射。
2.爲什麼使用反射?
打個比方,有時候我們需要一個函數可以處理各種類型的值。在不知道類型的情況下,你可能會這麼寫:
// 僞代碼
switch value := value.(type) {
case string:
// ...一些操作
case int:
// ...一些操作
case cbsStruct: // 自定義的結構體
// ...一些操作
// ...
}
有沒發現什麼問題?
這邊存在一個問題:類型很多,這個函數會寫的非常長,而且還可能存在自定的類型,也就是說這個判斷日後可能還要一直改,因爲無法知道未知值到底屬於什麼類型。
無法透視一個未知類型的時候,以上代碼其實不是很合理,這時候就需要有反射來幫忙你處理,反射使用TypeOf和ValueOf函數從接口中獲取目標對象的信息,輕鬆完成目的。
3.反射具體能做什麼?
1.獲取變量內部信息
reflect提供了兩種類型來進行訪問接口變量的內容:
類型 | 作用 |
---|---|
reflect.ValueOf() | 獲取輸入參數接口中的數據的值,如果爲空則返回0 <- 注意是0 |
reflect.TypeOf() | 動態獲取輸入參數接口中的值的類型,如果爲空則返回nil <- 注意是nil |
上代碼
package main
import (
"fmt"
"reflect"
)
func main() {
var name string = "咖啡色的羊駝"
// TypeOf會返回目標數據的類型,比如int/float/struct/指針等
reflectType := reflect.TypeOf(name)
// valueOf返回目標數據的的值,比如上文的"咖啡色的羊駝"
reflectValue := reflect.ValueOf(name)
fmt.Println("type: ", reflectType)
fmt.Println("value: ", reflectValue)
}
輸出:
type: string
value: 咖啡色的羊駝
更深一層:在以上操作發生的時候,反射將“接口類型的變量”轉爲了“反射的接口類型的變量”,比如上文實際上返回的是reflect.Value和reflect.Type的接口對象。(可以根據ide跟蹤一下相關函數返回類型便知)
2.struct的反射
package main
import (
"fmt"
"reflect"
)
type Student struct {
Id int
Name string
}
func (s Student) Hello(){
fmt.Println("我是一個學生")
}
func main() {
s := Student{Id: 1, Name: "咖啡色的羊駝"}
// 獲取目標對象
t := reflect.TypeOf(s)
// .Name()可以獲取去這個類型的名稱
fmt.Println("這個類型的名稱是:", t.Name())
// 獲取目標對象的值類型
v := reflect.ValueOf(s)
// .NumField()來獲取其包含的字段的總數
for i := 0; i < t.NumField(); i++ {
// 從0開始獲取Student所包含的key
key := t.Field(i)
// 通過interface方法來獲取key所對應的值
value := v.Field(i).Interface()
fmt.Printf("第%d個字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
}
// 通過.NumMethod()來獲取Student裏頭的方法
for i:=0;i<t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("第%d個方法是:%s:%v\n", i+1, m.Name, m.Type)
}
}
輸出:
這個類型的名稱是: Student
第1個字段是:Id:int = 1
第2個字段是:Name:string = 咖啡色的羊駝
第1個方法是:Hello:func(main.Student)
3.匿名或嵌入字段的反射
package main
import (
"reflect"
"fmt"
)
type Student struct {
Id int
Name string
}
type People struct {
Student // 匿名字段
}
func main() {
p := People{Student{Id: 1, Name: "咖啡色的羊駝"}}
t := reflect.TypeOf(p)
// 這裏需要加一個#號,可以把struct的詳情都給打印出來
// 會發現有Anonymous:true,說明是匿名字段
fmt.Printf("%#v\n", t.Field(0))
// 取出這個學生的名字的詳情打印出來
fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))
// 獲取匿名字段的值的詳情
v := reflect.ValueOf(p)
fmt.Printf("%#v\n", v.Field(0))
}
輸出:
reflect.StructField{Name:"Student", PkgPath:"", Type:(*reflect.rtype)(0x10aade0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x109f4e0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
main.Student{Id:1, Name:"咖啡色的羊駝"}
4.判斷傳入的類型是否是我們想要的類型
package main
import (
"reflect"
"fmt"
)
type Student struct {
Id int
Name string
}
func main() {
s := Student{Id: 1, Name: "咖啡色的羊駝"}
t := reflect.TypeOf(s)
// 通過.Kind()來判斷對比的值是否是struct類型
if k := t.Kind(); k == reflect.Struct {
fmt.Println("bingo")
}
num := 1;
numType := reflect.TypeOf(num)
if k := numType.Kind(); k == reflect.Int {
fmt.Println("bingo")
}
}
輸出:
bingo
bingo
5.通過反射修改內容
package main
import (
"reflect"
"fmt"
)
type Student struct {
Id int
Name string
}
func main() {
s := &Student{Id: 1, Name: "咖啡色的羊駝"}
v := reflect.ValueOf(s)
// 修改值必須是指針類型否則不可行
if v.Kind() != reflect.Ptr {
fmt.Println("不是指針類型,沒法進行修改操作")
return
}
// 獲取指針所指向的元素
v = v.Elem()
// 獲取目標key的Value的封裝
name := v.FieldByName("Name")
if name.Kind() == reflect.String {
name.SetString("小學生")
}
fmt.Printf("%#v \n", *s)
// 如果是整型的話
test := 888
testV := reflect.ValueOf(&test)
testV.Elem().SetInt(666)
fmt.Println(test)
}
輸出:
main.Student{Id:1, Name:"小學生"}
666
6.通過反射調用方法
package main
import (
"fmt"
"reflect"
)
type Student struct {
Id int
Name string
}
func (s Student) EchoName(name string){
fmt.Println("我的名字是:", name)
}
func main() {
s := Student{Id: 1, Name: "咖啡色的羊駝"}
v := reflect.ValueOf(s)
// 獲取方法控制權
// 官方解釋:返回v的名爲name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝
mv := v.MethodByName("EchoName")
// 拼湊參數
args := []reflect.Value{reflect.ValueOf("咖啡色的羊駝")}
// 調用函數
mv.Call(args)
}
輸出:
我的名字是: 咖啡色的羊駝
4.反射的一些小點
1.使用反射時需要先確定要操作的值是否是期望的類型,是否是可以進行“賦值”操作的,否則reflect包將會毫不留情的產生一個panic。
2.反射主要與Golang的interface類型相關,只有interface類型纔有反射一說。如果有興趣可以看一下TypeOf和ValueOf,會發現其實傳入參數的時候已經被轉爲接口類型了。
// 以下爲截取的源代碼
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
escapes(i)
return unpackEface(i)
}