天天看點

一篇帶你全面掌握go反射的用法

最重要的

你為什麼要用反射?這個問題請讀者自己回答。我強調一下反射的2個弊端:

  1. 代碼不易閱讀,不易維護,容易發生線上panic
  2. 性能很差,比正常代碼慢一到兩個數量級

go語言反射裡最重要的兩個概念是Type和Value,Type用于擷取類型相關的資訊(比如Slice的長度,struct的成員,函數的參數個數),Value用于擷取和修改原始資料的值(比如修改slice和map中的元素,修改struct的成員變量)。它們和go原始資料類型(比如int、float、map、struct等)的轉換方式如下圖

一篇帶你全面掌握go反射的用法

上圖中的這些方法在下文都會講到。接下來我們就先講Type,後講Value。

reflect.Type

如何得到Type

通過TypeOf()得到Type類型

typeI := reflect.TypeOf(1)       
typeS := reflect.TypeOf("hello") 
fmt.Println(typeI)               //int
fmt.Println(typeS)               //string

typeUser := reflect.TypeOf(&common.User{}) 
fmt.Println(typeUser)                     //*common.User
fmt.Println(typeUser.Kind())                 //ptr
fmt.Println(typeUser.Elem().Kind())    //struct
           

指針Type轉為非指針Type

typeUser := reflect.TypeOf(&common.User{}) 
typeUser2 := reflect.TypeOf(common.User{})
assert.IsEqual(typeUser.Elem(), typeUser2)
           

擷取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成員方法的資訊

typeUser := reflect.TypeOf(common.User{})
methodNum := typeUser.NumMethod() //成員方法的個數。接收者為指針的方法【不】包含在内
for i := 0; i < methodNum; i++ {
	method := typeUser.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
fmt.Println()

typeUser2 := reflect.TypeOf(&common.User{})
methodNum = typeUser2.NumMethod() //成員方法的個數。接收者為指針或值的方法【都】包含在内,也就是說值實作的方法指針也實作了(反之不成立)
for i := 0; i < methodNum; i++ {
	method := typeUser2.Method(i)
	fmt.Printf("method name:%s ,type:%s, exported:%t\n", method.Name, method.Type, method.IsExported())
}
           

擷取函數的資訊

func Add(a, b int) int {
	return a + b
}

typeFunc := reflect.TypeOf(Add) //擷取函數類型
fmt.Printf("is function type %t\n", typeFunc.Kind() == reflect.Func)
argInNum := typeFunc.NumIn()   //輸入參數的個數
argOutNum := typeFunc.NumOut() //輸出參數的個數
for i := 0; i < argInNum; i++ {
	argTyp := typeFunc.In(i)
	fmt.Printf("第%d個輸入參數的類型%s\n", i, argTyp)
}
for i := 0; i < argOutNum; i++ {
	argTyp := typeFunc.Out(i)
	fmt.Printf("第%d個輸出參數的類型%s\n", i, argTyp)
}
           

判斷類型是否實作了某接口

//通過reflect.TypeOf((*<interface>)(nil)).Elem()獲得接口類型。因為People是個接口不能建立執行個體,是以把nil強制轉為*common.People類型
typeOfPeople := reflect.TypeOf((*common.People)(nil)).Elem()
fmt.Printf("typeOfPeople kind is interface %t\n", typeOfPeople.Kind() == reflect.Interface)
t1 := reflect.TypeOf(common.User{})
t2 := reflect.TypeOf(&common.User{})
//如果值類型實作了接口,則指針類型也實作了接口;反之不成立
fmt.Printf("t1 implements People interface %t\n", t1.Implements(typeOfPeople))
           

reflect.Value

如果獲得Value

通過ValueOf()得到Value

iValue := reflect.ValueOf(1)
sValue := reflect.ValueOf("hello")
userPtrValue := reflect.ValueOf(&common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65,
	Height: 1.68,
})
fmt.Println(iValue)       //1
fmt.Println(sValue)       //hello
fmt.Println(userPtrValue) //&{7 傑克遜  65 1.68}
           

Value轉為Type

iType := iValue.Type()
sType := sValue.Type()
userType := userPtrValue.Type()
//在Type和相應Value上調用Kind()結果一樣的
fmt.Println(iType.Kind() == reflect.Int, iValue.Kind() == reflect.Int, iType.Kind() == iValue.Kind())  
fmt.Println(sType.Kind() == reflect.String, sValue.Kind() == reflect.String, sType.Kind() == sValue.Kind()) 
fmt.Println(userType.Kind() == reflect.Ptr, userPtrValue.Kind() == reflect.Ptr, userType.Kind() == userPtrValue.Kind())
           

指針Value和非指針Value互相轉換

userValue := userPtrValue.Elem()                    //Elem() 指針Value轉為非指針Value
fmt.Println(userValue.Kind(), userPtrValue.Kind())  //struct ptr
userPtrValue3 := userValue.Addr()                   //Addr() 非指針Value轉為指針Value
fmt.Println(userValue.Kind(), userPtrValue3.Kind()) //struct ptr
           

得到Value對應的原始資料

通過Interface()函數把Value轉為interface{},再從interface{}強制類型轉換,轉為原始資料類型。或者在Value上直接調用Int()、String()等一步到位。

fmt.Printf("origin value iValue is %d %d\n", iValue.Interface().(int), iValue.Int())
fmt.Printf("origin value sValue is %s %s\n", sValue.Interface().(string), sValue.String())
user := userValue.Interface().(common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user.Id, user.Name, user.Weight, user.Height)
user2 := userPtrValue.Interface().(*common.User)
fmt.Printf("id=%d name=%s weight=%.2f height=%.2f\n", user2.Id, user2.Name, user2.Weight, user2.Height)
           

空Value的判斷

var i interface{} //接口沒有指向具體的值
v := reflect.ValueOf(i)
fmt.Printf("v持有值 %t, type of v is Invalid %t\n", v.IsValid(), v.Kind() == reflect.Invalid)

var user *common.User = nil
v = reflect.ValueOf(user) //Value指向一個nil
if v.IsValid() {
	fmt.Printf("v持有的值是nil %t\n", v.IsNil()) //調用IsNil()前先確定IsValid(),否則會panic
}

var u common.User //隻聲明,裡面的值都是0值
v = reflect.ValueOf(u)
if v.IsValid() {
	fmt.Printf("v持有的值是對應類型的0值 %t\n", v.IsZero()) //調用IsZero()前先確定IsValid(),否則會panic
}
           

通過Value修改原始資料的值

var i int = 10
var s string = "hello"
user := common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}

valueI := reflect.ValueOf(&i) //由于go語言所有函數傳的都是值,是以要想修改原來的值就需要傳指針
valueS := reflect.ValueOf(&s)
valueUser := reflect.ValueOf(&user)
valueI.Elem().SetInt(8) //由于valueI對應的原始對象是指針,通過Elem()傳回指針指向的對象
valueS.Elem().SetString("golang")
valueUser.Elem().FieldByName("Weight").SetFloat(68.0) //FieldByName()通過Name傳回類的成員變量
           

強調一下,要想修改原始資料的值,給ValueOf傳的必須是指針,而指針Value不能調用Set和FieldByName方法,是以得先通過Elem()轉為非指針Value。

未導出成員的值不能通過反射進行修改。

addrValue := valueUser.Elem().FieldByName("addr")
if addrValue.CanSet() {
	addrValue.SetString("北京")
} else {
	fmt.Println("addr是未導出成員,不可Set") //以小寫字母開頭的成員相當于是私有成員
}
           

通過Value修改Slice

users := make([]*common.User, 1, 5) //len=1,cap=5
users[0] = &common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}

sliceValue := reflect.ValueOf(&users) //準備通過Value修改users,是以傳users的位址
if sliceValue.Elem().Len() > 0 {      //取得slice的長度
	sliceValue.Elem().Index(0).Elem().FieldByName("Name").SetString("令狐一刀")
	fmt.Printf("1st user name change to %s\n", users[0].Name)
}
           

甚至可以修改slice的cap,新的cap必須位于原始的len到cap之間,即隻能把cap改小。

sliceValue.Elem().SetCap(3)           

通過把len改大,可以實作向slice中追加元素的功能。

sliceValue.Elem().SetLen(2)
//調用reflect.Value的Set()函數修改其底層指向的原始資料
sliceValue.Elem().Index(1).Set(reflect.ValueOf(&common.User{
	Id:     8,
	Name:   "李達",
	Weight: 80,
	Height: 180,
}))
fmt.Printf("2nd user name %s\n", users[1].Name)
           

修改map

Value.SetMapIndex()函數:往map裡添加一個key-value對

Value.MapIndex()函數: 根據Key取出對應的map

u1 := &common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
u2 := &common.User{
	Id:     8,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
userMap := make(map[int]*common.User, 5)
userMap[u1.Id] = u1

mapValue := reflect.ValueOf(&userMap)                                                         //準備通過Value修改userMap,是以傳userMap的位址
mapValue.Elem().SetMapIndex(reflect.ValueOf(u2.Id), reflect.ValueOf(u2))                      //SetMapIndex 往map裡添加一個key-value對
mapValue.Elem().MapIndex(reflect.ValueOf(u1.Id)).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map
for k, user := range userMap {
	fmt.Printf("key %d name %s\n", k, user.Name)
}
           

調用函數

valueFunc := reflect.ValueOf(Add) //函數也是一種資料類型
typeFunc := reflect.TypeOf(Add)
argNum := typeFunc.NumIn()            //函數輸入參數的個數
args := make([]reflect.Value, argNum) //準備函數的輸入參數
for i := 0; i < argNum; i++ {
	if typeFunc.In(i).Kind() == reflect.Int {
		args[i] = reflect.ValueOf(3) //給每一個參數都賦3
	}
}
sumValue := valueFunc.Call(args) //傳回[]reflect.Value,因為go語言的函數傳回可能是一個清單
if typeFunc.Out(0).Kind() == reflect.Int {
	sum := sumValue[0].Interface().(int) //從Value轉為原始資料類型
	fmt.Printf("sum=%d\n", sum)
}
           

調用成員方法

common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
valueUser := reflect.ValueOf(&user)              //必須傳指針,因為BMI()在定義的時候它是指針的方法
bmiMethod := valueUser.MethodByName("BMI")       //MethodByName()通過Name傳回類的成員變量
resultValue := bmiMethod.Call([]reflect.Value{}) //無參數時傳一個空的切片
result := resultValue[0].Interface().(float32)
fmt.Printf("bmi=%.2f\n", result)

//Think()在定義的時候用的不是指針,valueUser可以用指針也可以不用指針
thinkMethod := valueUser.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})

valueUser2 := reflect.ValueOf(user)
thinkMethod = valueUser2.MethodByName("Think")
thinkMethod.Call([]reflect.Value{})
           

建立對象

建立struct

user :=t := reflect.TypeOf(common.User{})
value := reflect.New(t) //根據reflect.Type建立一個對象,得到該對象的指針,再根據指針提到reflect.Value
value.Elem().FieldByName("Id").SetInt(10)
user := value.Interface().(*common.User) //把反射類型轉成go原始資料類型Call([]reflect.Value{})
           

建立slice

var slice []common.User
sliceType := reflect.TypeOf(slice)
sliceValue := reflect.MakeSlice(sliceType, 1, 3)
sliceValue.Index(0).Set(reflect.ValueOf(common.User{
	Id:     8,
	Name:   "李達",
	Weight: 80,
	Height: 180,
}))
users := sliceValue.Interface().([]common.User)
fmt.Printf("1st user name %s\n", users[0].Name)
           

建立map

var userMap map[int]*common.User
mapType := reflect.TypeOf(userMap)
// mapValue:=reflect.MakeMap(mapType)
mapValue := reflect.MakeMapWithSize(mapType, 10)

user := &common.User{
	Id:     7,
	Name:   "傑克遜",
	Weight: 65.5,
	Height: 1.68,
}
key := reflect.ValueOf(user.Id)
mapValue.SetMapIndex(key, reflect.ValueOf(user))                    //SetMapIndex 往map裡添加一個key-value對
mapValue.MapIndex(key).Elem().FieldByName("Name").SetString("令狐一刀") //MapIndex 根據Key取出對應的map
userMap = mapValue.Interface().(map[int]*common.User)
fmt.Printf("user name %s %s\n", userMap[7].Name, user.Name)
           

reflect包裡除了MakeSlice()和MakeMap(),還有MakeChan()和MakeFunc()。

一篇帶你全面掌握go反射的用法