反射簡介

尹成發表於2018-11-16

反射概述

  • 通常的程式邏輯是:編碼時先寫好劇本,執行時按照寫好的劇本演
  • 何時建立一個什麼例項,給哪個屬性賦什麼值,然後呼叫它的哪個方法都毫釐不差
  • 但能否在執行時動態地生成“劇本”呢?
  • 根據具體的業務需要見機行事,動態地生成一個不知道具體型別會是什麼的例項,動態地訪問一個無法提前預知的屬性或方法
  • 答案是可以的,這便引出了今天的主角——反射

應用場景舉例:匯出商品列表到Excel

  • 需求是:不管使用者在介面上看到什麼商品列表,當它捅一下匯出按鈕,就將該商品的所有屬性和值寫出為檔案;
  • 本例的難點是:我們無法預知使用者會選擇匯出什麼型別的商品資料、它有哪些屬性,也就無法相應地去建立Excel資料表的列(讀寫Excel有相應的庫,你也可以選擇寫出為文字檔案、寫出到資料庫表中等等)
  • 因為商品的種類太多,如果用正射去做,那麼有多少商品型別我們就要寫多少個switch或if分支,然後在每一個分支里根據當前分支的具體商品型別去構造相應的Excel資料列,這顯然是蹩腳並且很難維護和擴充套件的
  • 而通過反射來做就易如反掌了,管你來的是什麼商品例項,立即動態解析得到其類別、所有屬性名值,然後根據屬性名去建立Excel的列,根據屬性值去做資料的填充和寫入

定義結構體

type Human struct {
	Name string "姓名"
	Age  int    "年齡"
}

func (h *Human) GetName() string {
	fmt.Println("GetName called",h.Name)
	return h.Name
}

func (h *Human) SetName(name string) {
	fmt.Println("SetName called:", name)
	h.Name = name
}

func (h *Human) Eat(food string, grams int) (power int) {
	fmt.Println("Eat called:", food, grams)
	return 5 * grams
}

反射功能一覽

func main() {
	
	//建立物件(在實際開發中可以是任意未知型別)
	h := Human{"bill", 60}

	//獲取任意物件的型別和值
	getObjTypeValue(h)

	//獲得任意物件的所有屬性
	getObjFields(h)

	//獲取任意物件的所有方法
	getObjMethods(&h)

	//修改物件任意屬性的值
	modifyFieldValue(&h)

	//動態呼叫物件的任意方法
	callMethods(&h)
	fmt.Println("after:h=", h)
}

獲取任意物件的型別和值

func getObjTypeValue(obj interface{}) {
	oType := reflect.TypeOf(obj)
	oKind := oType.Kind()
	fmt.Println(oType, oKind)

	oValue := reflect.ValueOf(obj)
	fmt.Println(oValue)
}

獲得任意物件的所有屬性

func getObjFields(obj interface{}) {
	oType := reflect.TypeOf(obj)
	oValue := reflect.ValueOf(obj)

	fieldsCount := oType.NumField()
	for i := 0; i < fieldsCount; i++ {

		//從物件值中獲取第i個屬性的值,進而值的“正射”形式
		fValue := oValue.Field(i).Interface()
		structField := oType.Field(i)
		fmt.Println(structField.Name, structField.Type, structField.Tag, fValue)
	}
}

獲取任意物件的所有方法

func getObjMethods(obj interface{}) {
	oType := reflect.TypeOf(obj)
	fmt.Println(oType.NumMethod())
	for i := 0; i < oType.NumMethod(); i++ {
		method := oType.Method(i)
		fmt.Println(method.Name, method.Type)
	}
}

修改物件任意屬性的值

func modifyFieldValue(objPtr interface{}) {
	//得到【指標的Value】
	oPtrValue := reflect.ValueOf(objPtr)
	//得到【指標所指向的值(結構體)的Value】
	oValue := oPtrValue.Elem()
	fmt.Println(oValue)

	//遍歷所有屬性的值
	for i := 0; i < oValue.NumField(); i++ {

		//根據序號拿到【屬性的Value】
		fValue := oValue.Field(i)
		//拿到屬性值的原生型別
		fKind := fValue.Kind()
		//fmt.Println(fKind)

		//根據不同的原生型別設定為不同的值
		switch fKind {
		case reflect.String:
			fValue.SetString("張全蛋")
		case reflect.Int:
			fValue.SetInt(99)
		case reflect.Bool:
			fValue.SetBool(false)
		default:
			fmt.Println("設定為其他值...")
		}
	}

}

動態呼叫物件的任意方法

func callMethods(objPtr interface{}) {

	//要通過物件的oType拿取方法的引數列表(oType.In(i))
	oType := reflect.TypeOf(objPtr)
	//要通過物件的oValue拿取方法的具體實現(methodValue)
	oValue := reflect.ValueOf(objPtr)

	//根據方法的數量進行遍歷
	for i := 0; i < oType.NumMethod(); i++ {

		//預定義要傳值的引數切片[]Value
		args := make([]reflect.Value, 0)

		//從物件的oType拿到當前的方法的methodType
		methodType := oType.Method(i).Type

		//根據引數個數進行遍歷
		//為每個引數亂懟一個同種型別反射值,丟入引數列表
		//內層迴圈走完時,當前方法的引數列表就形成了
		for j:=0;j<methodType.NumIn();j++{

			//從方法的methodType獲取當前引數artType
			artType := methodType.In(j)
			//再獲取引數型別artType的原生型別
			argKind := artType.Kind()

			//根據不同的引數原生型別亂懟相同型別的值
			switch argKind {

			case reflect.String:
				//獲取字串"一坨翔"的反射值Value,丟入引數列表
				args = append(args,reflect.ValueOf("一坨翔"))

			case reflect.Int:
				args = append(args,reflect.ValueOf(100))
			case reflect.Bool:
				args = append(args,reflect.ValueOf(false))
			}
		}

		//從物件值oValue中獲取當前方法值methodValue
		methodValue := oValue.Method(i)

		//使用引數列表+方法值,反射呼叫當前方法
		methodValue.Call(args)
	}
}

學院Go語言視訊主頁
https://edu.csdn.net/lecturer/1928

[清華團隊帶你實戰區塊鏈開發]
(https://ke.qq.com/course/344443?tuin=3d17195d)
掃碼獲取海量視訊及原始碼 QQ群:721929980
在這裡插入圖片描述