go學習(一)-反射

介紹

  • 反射是一種機制,是指可以動態的獲取對象信息(名稱、屬性、類型等)、操作對象(創建對象、修改屬性值、調用方法等)

  • 意義:

    • 因爲有的時候,並不知道對象具體是什麼類型,有哪些屬性和方法
    • 便於編寫通用的框架,實現松耦合、高複用。比如,ORM庫操作、json序列化等
  • golang的反射

    • golang提供了reflect包實現反射處理,golang的反射是對接口變量的動態類型(type)和動態值(value)相關的操作。
  • reflect包的核心如下:

    • reflect.TypeOf(i interface{}) Type:獲取一個“空接口”的動態類型,封裝成reflect.Type返回(內部爲reflect.rType實例)(備註:入參爲副本拷貝)
    • reflect.ValueOf(i interface{}) Value:獲取一個“空接口”的所有信息(備註:入參爲副本拷貝)
    • Value:是一個結構體,擁有“動態對象”的所有信息
    • Type:這是一個接口
    • 此外,還有2個包,可以瞭解一下:
      * Method:定義了方法的屬性名而已
      * Kind:定義了元數據
  • 圖示:
    在這裏插入圖片描述


示例介紹

入門示例
  • 通過反射操作一個int:
func main() {
	// 基本數據類型的反射操作
	var i int = 10
	fmt.Println("獲取i的Type:", reflect.TypeOf(i))		// 獲取反射類型
	fmt.Println("獲取i的Value:", reflect.ValueOf(i))	// 獲取反射值
	
	// 開始修改i的值,需要用指針,因爲ValueOf的入參爲值拷貝
	iOf := reflect.ValueOf(&i)
	iOf.Elem().SetInt(20)
	fmt.Println("修改後i的值爲:", i)

	// 將Value再轉回指針:
	i2 := iOf.Interface().(*int)
	fmt.Println("修改後i2的值爲:", *i2)
}
  • 輸出結果:
獲取i的Type: int
獲取i的Value: 10
修改後i的值爲: 20
修改後i2的值爲: 20
這是一個簡單的示例,可以看到,通過反射,正常的修改了i的值。
並且,反射後的Value,也可以正常的轉換回int指針
Value操作
  • 上面的簡單的反射操作,核心在於Value類的操作,而Value包含了很多方法(http://docscn.studygolang.com/pkg/reflect/#Copy)
// 設置Value值:
//func (v Value) Set(x Value)				// 設置一個Value值,比如int類型的:i.Set(reflect.ValueOf(44))
//func (v Value) SetBool(x bool)			// 設置bool值,如下都是相同的調用方式:i.Elem().SetBool(false)
//func (v Value) SetBytes(x []byte)
//func (v Value) SetCap(n int)
//func (v Value) SetComplex(x complex128)
//func (v Value) SetFloat(x float64)
//func (v Value) SetInt(x int64)
//func (v Value) SetLen(n int)
//func (v Value) SetMapIndex(key, val Value)
//func (v Value) SetPointer(x unsafe.Pointer)
//func (v Value) SetString(x string)
//func (v Value) SetUint(x uint64)

fmt.Println("Addr():", iOf.Elem().Addr())
fmt.Println("Bool():", reflect.ValueOf(false).Bool())
fmt.Println("Bytes():", reflect.ValueOf(make([]byte, 3)).Bytes())
//func (v Value) Addr() Value				// 獲取地址信息:iOf.Elem().Addr())
//func (v Value) Bool() bool				// 獲取bool型的反射:reflect.ValueOf(false).Bool()
//func (v Value) Bytes() []byte				// 獲取字節數組: reflect.ValueOf(make([]byte, 3)).Bytes()

//func (v Value) Call(in []Value) []Value
//func (v Value) CallSlice(in []Value) []Value

fmt.Println("CanAddr():", reflect.ValueOf(1).CanAddr())
fmt.Println("CanInterface():", reflect.ValueOf(1).CanInterface())
fmt.Println("CanSet():", reflect.ValueOf(1).CanSet())
//func (v Value) CanAddr() bool				// 是否可以獲取Addr:reflect.ValueOf(1).CanAddr()
//func (v Value) CanInterface() bool		// 是否可以轉化爲Interface:reflect.ValueOf(1).CanInterface()
//func (v Value) CanSet() bool				// 是否可以修改值:reflect.ValueOf(1).CanSet()
//func (v Value) Cap() int					// 獲取容量值:如果是非數組、管道、切片,則會異常panic
//func (v Value) Close()					// 關閉channel對象,非channel時拋出異常panic
//func (v Value) Complex() complex128		// 返回複數的underlying value(實部?)
//func (v Value) Convert(t Type) Value		// 將v類型轉爲t類型
//func (v Value) Elem() Value				// 返回v的值、或v指向的值(指針場景)
//func (v Value) Field(i int) Value			// 返回struct的第i個字段
//func (v Value) FieldByIndex(index []int) Value	// 返回一個嵌套的field列表
//func (v Value) FieldByName(name string) Value		// 根據名字在struct中查找字段
//func (v Value) FieldByNameFunc(match func(string) bool) Value	// 按照函數規則查找字段(函數式編程)

var f1 float64 = 1.234
fmt.Println("f1 Float(): ", reflect.ValueOf(f1).Float())
//func (v Value) Float() float64			// 返回float64 Value的真實值
//func (v Value) Index(i int) Value			// 返回第i個Value,僅限於:數組、管道、切片
//func (v Value) Int() int64				// 返回int值,僅限於int類型:int8/16/32/64
//func (v Value) Interface() (i interface{})// 將Value轉爲interface
//func (v Value) InterfaceData() [2]uintptr	// 沒理解????
//func (v Value) IsNil() bool				// 判斷Value是否爲nil
//func (v Value) IsValid() bool				// 判斷Value是否有值,如果爲0時返回false
//func (v Value) Kind() Kind				// 返回kind類型
//func (v Value) Len() int					// 返回Value長度,僅限於string、數組、管道、切片
//func (v Value) MapIndex(key Value) Value	// 返回map類型的key對應的value
//func (v Value) MapKeys() []Value			// 返回map類型的key列表
//func (v Value) Method(i int) Value		// 返回第i個方法(struct中方法按照字母的字典序排序)
//func (v Value) MethodByName(name string) Value	// 按照name查找方法
//func (v Value) NumField() int				// 返回字段數
//func (v Value) NumMethod() int			// 返回方法數
//func (v Value) OverflowComplex(x complex128) bool	// 判斷Value是否可以表示x complex128
//func (v Value) OverflowFloat(x float64) bool		// 判斷Value是否可以表示x float64
//func (v Value) OverflowInt(x int64) bool			// 判斷Value是否可以表示x int64
//func (v Value) OverflowUint(x uint64) bool		// 判斷Value是否可以表示x uint64
//func (v Value) Pointer() uintptr			// 返回v的值,將v的值作爲指針
//func (v Value) Recv() (x Value, ok bool)	// ???
//func (v Value) Send(x Value)				// ???

//func (v Value) Slice(i, j int) Value		// 返回v中到j的切片
//func (v Value) Slice3(i, j, k int) Value	// 返回v的三維的切片???
//func (v Value) String() string			// 返回v的值爲string
//func (v Value) TryRecv() (x Value, ok bool)	// ???
//func (v Value) TrySend(x Value) bool		// ??/
//func (v Value) Type() Type				// 返回v的類型
//func (v Value) Uint() uint64				// 返回uint64的value值
//func (v Value) UnsafeAddr() uintptr		// 返回指向v的的值的指針---注意和Pointer()的區別

備註:方法裏面含有一些channel的操作,這裏先不做介紹

示例:操作struct
  • Value包含了很多方法,可以通過一個簡單的struct操作來學習一下
  • 聲明一個Student struct:
// 待反射的類,定義三個屬性、三個方法
type Student struct {
	Name  string  `json:"name" nickname:"name"`
	Age   int     `json:"age" nickname:"old"`
	Place *string `json:"place" nickname:"pla"`
}

func (s Student) Say() {
	fmt.Println("Student say... ...")
}

func (s Student) Play(a string, b string) {
	fmt.Println("Student play with ", a, b)
}

func (s Student) Cal(a int, b int) int {
	fmt.Println("Student cal a+b=", a+b)
	return a + b
}
  • 測試:
func main() {
	fmt.Println("===========================================================")
	var str string = "杭州"
	student := Student{
		Name:  "zhangsan",
		Age:   20,
		Place: &str,
	}
	fmt.Println("獲取的Student類型:", reflect.TypeOf(student))
	fmt.Println("獲取的Student值爲:", reflect.ValueOf(student))
	fmt.Println("==========================開始獲取student的屬性方法==========================")
	sOf := reflect.ValueOf(student)
	sTy := reflect.TypeOf(student)
	fmt.Println("Student的字段個數:", sOf.NumField())
	fmt.Println("Student的方法個數:", sOf.NumMethod())
	for i:=0; i<sOf.NumField(); i++ {
		fmt.Println("Student的第", i ,"個字段爲:", sOf.Field(i))
		fmt.Println("Student的第", i ,"個方法爲:", sOf.Method(i))
		get := sTy.Field(i).Tag.Get("json")
		if get != "" {
			fmt.Println("Student的第", i ,"個字段的tag爲:", get)
		}
	}

	fmt.Println("==========================修改值==========================")
	// 下面兩種方式都可以
	//reflect.ValueOf(&student).Elem().FieldByName("Name").SetString("lisi")
	reflect.ValueOf(&student).Elem().Field(0).SetString("lisi")
	fmt.Println("Student修改後:", student)

	fmt.Println("==========================反射調用方法==========================")
	fmt.Println("調用方法:", sOf.MethodByName("Say").Call(nil))
	var params []reflect.Value
	val1 := reflect.ValueOf(2)
	val2 := reflect.ValueOf(3)
	params = append(params, val1)
	params = append(params, val2)
	fmt.Println("調用方法:", sOf.MethodByName("Cal").Call(params))
}
  • 輸出結果:
===========================================================
獲取的Student類型: main.Student
獲取的Student值爲: {zhangsan 20 0xc000010200}
==========================開始獲取student的屬性方法==========================
Student的字段個數: 3
Student的方法個數: 3
Student的第 0 個字段爲: zhangsan
Student的第 0 個方法爲: 0x1087960
Student的第 0 個字段的tag爲: name
Student的第 1 個字段爲: 20
Student的第 1 個方法爲: 0x1087960
Student的第 1 個字段的tag爲: age
Student的第 2 個字段爲: 0xc000010200
Student的第 2 個方法爲: 0x1087960
Student的第 2 個字段的tag爲: place
==========================修改值==========================
Student修改後: {lisi 20 0xc000010200}
==========================反射調用方法==========================
Student say... ...
調用方法: []
Student cal a+b= 5
調用方法: [<int Value>]

注意,當需要修改結構體裏的字段時,需要傳入指針類型

通過反射創建結構體
  • 代碼:
fmt.Println("==========================反射創建Student==========================")
stuType := reflect.TypeOf(student)
value := reflect.New(stuType)
student1 := value.Interface().(*Student)
student1.Name = "fanshe"
student1.Age = 200
fmt.Println("反射創建的student", value)
  • 結果
    可以看到,正常的創建了Student類
==========================反射創建Student==========================
反射創建的student &{fanshe 200 <nil>}

其它

  • 關於reflect.Kind
    它是一個常量類集合:
const (
    Invalid Kind = iota // iota是一個常量0,用於const聲明中,表示序列0,後續的每一行數會加一
    Bool // 1
    Int   // 2
    Int8  // 3     因爲iota標識了序列起始爲0,故下面的每一行都+1
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

這裏有一點需要解釋一下:iota
a. 在const中聲明一次iota即可,後續的聲明都會被覆蓋掉
b. iota聲明是以行爲標識,一行中聲明多個變量時,這些變量都是一樣的結果

  • 測試一下iota:
fmt.Println("==========================測試iota==========================")
const (
	a = iota
	b
	c
	d string = "ssss"
	e
)
fmt.Printf("a=%v, b=%v, c=%v, d=%v, e=%v \n", a, b, c, d, e)

const (
	a1 = iota
	b11, b12 = iota, iota
	c1 = iota
)
fmt.Printf("a1=%v, b11=%v, b12=%v, c1=%v \n", a1, b11, b12, c1)

#########################################
//輸出結果:
==========================測試iota==========================
a=0, b=1, c=2, d=ssss, e=ssss 
a1=0, b11=1, b12=1, c1=2 

可以看到:第二個聲明的b11和b12在同一行,並且值都爲1(本身聲明的iota會被覆蓋掉)

  • reflect.Method
    Method是一個結構體,暫無對外暴露的成員方法(在反射的內部調用)
type Method struct {
    // Name is the method name.
    // PkgPath is the package path that qualifies a lower case (unexported)
    // method name.  It is empty for upper case (exported) method names.
    // The combination of PkgPath and Name uniquely identifies a method
    // in a method set.
    // See http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string

    Type  Type  // method type
    Func  Value // func with receiver as first argument
    Index int   // index for Type.Method
}
  • Type和Kind的區別:
    Type是類型,Kind是類別:Kind的範圍大於Type:對於基本類型(int/float/string…),Kind和Type返回一樣,但是對於struct,Kind返回“struct”,而Type返回具體的結構體名“Student”

參考

https://zhuanlan.zhihu.com/p/53114706
https://www.jianshu.com/p/9816a7a551cd
https://www.jianshu.com/p/32e4cf8ffffb
https://www.jianshu.com/p/444b55edf32e
https://zhuanlan.zhihu.com/p/53114706

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章