什么是反射
官方關于反射定義:
Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
(在計算機領域,反射是一種讓程序——主要是通過類型——理解其自身結構的一種能力。它是元編程的組成之一,同時它也是一大引人困惑的難題。)
維基百科關于反射的定義:
在計算機科學中,反射是指計算機程序在運行時(Run time)可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力。用比喻來說,反射就是程序在運行的時候能夠“觀察”并且修改自己的行為。
《Go語言圣經》關于反射的定義:
Go 語言提供了一種機制在運行時更新變量和檢查它們的值、調用它們的方法,但是在編譯時并不知道這些變量的具體類型,這稱為反射機制。
Go 語言是靜態(tài)編譯類語言,比如在定義一個變量的時候,已經知道了它是什么類型。但是有些事情只有在運行時才知道。比如定義了一個函數(shù),它有一個interface{}類型的參數(shù),這也就意味著調用者可以傳遞任何類型的參數(shù)給這個函數(shù)。在這種情況下,如果想知道調用者傳遞的是什么類型的參數(shù),就需要用到反射。如果想知道一個結構體有哪些字段和方法,也需要反射。
根據以上定義,可以得出:
反射是指在程序運行時對程序本身進行訪問和修改的能力。程序在編譯時,變量被轉換為內存地址,變量名不會被編譯器寫入到可執(zhí)行部分。在運行程序時,程序無法獲取自身的信息。
支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱、類型信息、結構體信息等整合到可執(zhí)行文件中,并給程序提供接口訪問反射信息,這樣就可以在程序運行期獲取類型的反射信息,并且有能力修改它們。
變量的內在機制
-
Go語言中的變量是分為兩部分的:
- 類型信息(
type):預先定義好的元信息。 - 值信息(
value):程序運行過程中可動態(tài)變化的。
理解這一點就知道為什么
nil != nil了 - 類型信息(
type包括static type和concrete type. 簡單來說static type是在編碼時確定的類型(如int、string等),concrete type是runtime系統(tǒng)確定的類型。類型斷言能否成功,取決于變量的
concrete type,而不是static type。因此,一個reader變量如果它的concrete type也實現(xiàn)了write方法的話,它也可以被類型斷言為writer
Go是靜態(tài)類型語言。每個變量都擁有一個靜態(tài)類型,這意味著每個變量的類型在編譯時都是確定的:int,float32, *AutoType, []byte, chan []int 諸如此類。
在反射的概念中, 編譯時就知道變量類型的是靜態(tài)類型;運行時才知道一個變量類型的叫做動態(tài)類型。
- 靜態(tài)類型: 靜態(tài)類型就是變量聲明時的賦予的類型:
type MyInt int // int 就是靜態(tài)類型
type A struct{
Name string // string就是靜態(tài)
}
var i *int // *int就是靜態(tài)類型
- 動態(tài)類型:運行時給這個變量賦值時,這個值的類型(如果值為nil的時候沒有動態(tài)類型)。一個變量的動態(tài)類型在運行時可能改變,這主要依賴于它的賦值(前提是這個變量是接口類型)。
var A interface{} // 靜態(tài)類型interface{}
A = 10 // 靜態(tài)類型為interface{} 動態(tài)為int
A = "String" // 靜態(tài)類型為interface{} 動態(tài)為string
var M *int
A = M // A的值可以改變
Go語言的反射就是建立在類型之上的,Golang的指定類型的變量的類型是靜態(tài)的,在創(chuàng)建變量的時候就已經確定,反射主要與Golang的interface類型相關,只有interface類型才有反射一說。
在Golang的實現(xiàn)中,每個interface變量都有一個對應pair,pair中記錄了實際變量的值和類型(在接口介紹時有描述):
(value, type)
value是實際變量值,type是實際變量的類型。一個interface{}類型的變量包含了2個指針,一個指針指向值的類型(對應concrete type),另外一個指針指向實際的值(對應value)。
例如,創(chuàng)建類型為*os.File的變量,然后將其賦給一個接口變量r:
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
var r io.Reader
r = tty
接口變量r的pair中將記錄如下信息:(tty, *os.File),這個pair在接口變量的連續(xù)賦值過程中是不變的,將接口變量r賦給另一個接口變量w:
var w io.Writer
w = r.(io.Writer)
接口變量w的pair與r的pair相同,都是:(tty, *os.File),即使w是空接口類型,pair也是不變的。
interface及其pair的存在,是Golang中實現(xiàn)反射的前提,理解了pair,就更容易理解反射。反射就是用來檢測存儲在接口變量內部(值value;類型concrete type) pair對的一種機制。
所以要理解兩個基本概念 Type 和 Value,它們也是 Go語言包中 reflect 空間里最重要的兩個類型。
reflect包
Go程序在運行時使用reflect包訪問程序的反射信息。
之前介紹過interface,空接口可以存儲任意類型的變量,那如何知道這個空接口保存的數(shù)據是什么呢? 反射就是在運行時動態(tài)的獲取一個變量的類型信息和值信息。
在Go語言的反射機制中,任何接口值都由是一個具體類型和具體類型的值兩部分組成的。 在Go語言中反射的相關功能由內置的reflect包提供,任意接口值在反射中都可以理解為由reflect.Type和reflect.Value兩部分組成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf兩個函數(shù)來獲取任意對象的Value和Type。
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero
// 翻譯一下:ValueOf用來獲取輸入參數(shù)接口中的數(shù)據的值,如果接口為空則返回0
func ValueOf(i interface{}) Value {...}
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
// 翻譯一下:TypeOf用來動態(tài)獲取輸入參數(shù)接口中的值的類型,如果接口為空則返回nil
func TypeOf(i interface{}) Type {...}
TypeOf
Type
reflect.Value 可以用于與值有關的操作中,而如果是和變量類型本身有關的操作,則最好使用 reflect.Type,比如要獲取結構體對應的字段名稱或方法。
和 reflect.Value 不同,reflect.Type 是一個接口,而不是一個結構體,所以也只能使用它的方法。
以下 reflect.Type 接口常用的方法。從列表來看,大部分都和 reflect.Value 的方法功能相同。
type Type interface {
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
//以下這些方法和Value結構體的功能相同
Kind() Kind
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
}
其中幾個特有的方法如下:
- Implements 方法用于判斷是否實現(xiàn)了接口 u;
- AssignableTo 方法用于判斷是否可以賦值給類型 u,其實就是是否可以使用 =,即賦值運算符;
- ConvertibleTo 方法用于判斷是否可以轉換成類型 u,其實就是是否可以進行類型轉換;
- Comparable 方法用于判斷該類型是否是可比較的,其實就是是否可以使用關系運算符進行比較。
要反射獲取一個變量的 reflect.Type,可以通過函數(shù) reflect.TypeOf(),程序通過類型對象可以訪問任意值的類型信息。
func main() {
//反射操作:通過反射,可以獲取一個接口類型變量的 類型
var x float64 =3.4
fmt.Println("type:",reflect.TypeOf(x)) //type: float64
}
type name和type kind
在反射中關于類型還劃分為兩種:類型(Type)和種類(Kind)。因為在Go語言中可以使用type關鍵字構造很多自定義類型,而種類(Kind)就是指底層的類型,但在反射中,當需要區(qū)分指針、結構體等大品種的類型時,就會用到種類(Kind)。 舉個例子,定義了兩個指針類型和兩個結構體類型,通過反射查看它們的類型和種類。
type myInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
func main() {
var a *float32 // 指針
var b myInt // 自定義類型
var c rune // 類型別名
reflectType(a) // type: kind:ptr
reflectType(b) // type:myInt kind:int64
reflectType(c) // type:int32 kind:int32
type person struct {
name string
age int
}
type book struct{ title string }
var d = person{
name: "張三",
age: 25,
}
var e = book{title: "《Go語言圣經》"}
reflectType(d) // type:person kind:struct
reflectType(e) // type:book kind:struct
}
Go語言的反射中像數(shù)組、切片、Map、指針等類型的變量,它們的.Name()都是返回空。
在reflect包中定義的Kind類型如下:
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota // 非法類型
Bool // 布爾型
Int // 有符號整型
Int8 // 有符號8位整型
Int16 // 有符號16位整型
Int32 // 有符號32位整型
Int64 // 有符號64位整型
Uint // 無符號整型
Uint8 // 無符號8位整型
Uint16 // 無符號16位整型
Uint32 // 無符號32位整型
Uint64 // 無符號64位整型
Uintptr // 指針
Float32 // 單精度浮點數(shù)
Float64 // 雙精度浮點數(shù)
Complex64 // 64位復數(shù)類型
Complex128 // 128位復數(shù)類型
Array // 數(shù)組
Chan // 通道
Func // 函數(shù)
Interface // 接口
Map // 映射
Ptr // 指針
Slice // 切片
String // 字符串
Struct // 結構體
UnsafePointer // 底層指針
)
通過 reflect.Type 還可以判斷是否實現(xiàn)了某接口。以 person 結構體為例,判斷它是否實現(xiàn)了接口 fmt.Stringer 和 io.Writer:
func main() {
p:=person{Name: "張三",Age: 20}
pt:=reflect.TypeOf(p)
stringerType:=reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
writerType:=reflect.TypeOf((*io.Writer)(nil)).Elem()
fmt.Println("是否實現(xiàn)了fmt.Stringer:",pt.Implements(stringerType))
fmt.Println("是否實現(xiàn)了io.Writer:",pt.Implements(writerType))
}
盡可能通過類型斷言的方式判斷是否實現(xiàn)了某接口,而不是通過反射。
通過 Implements 方法來判斷是否實現(xiàn)了 fmt.Stringer 和 io.Writer 接口,運行結果:
是否實現(xiàn)了fmt.Stringer: false
是否實現(xiàn)了io.Writer: false
ValueOf
reflect.ValueOf()返回的是reflect.Value類型,其中包含了原始值的值信息。reflect.Value與原始值之間可以互相轉換。
reflect.Value 被定義為一個 struct 結構體,它的定義如下面所示:
// Value is the reflection interface to a Go value.
//
// Not all methods apply to all kinds of values. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of value before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run time panic.
//
// The zero Value represents no value.
// Its IsValid method returns false, its Kind method returns Invalid,
// its String method returns "<invalid Value>", and all other methods panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
//
// A Value can be used concurrently by multiple goroutines provided that
// the underlying Go value can be used concurrently for the equivalent
// direct operations.
//
// To compare two Values, compare the results of the Interface method.
// Using == on two Values does not compare the underlying values
// they represent.
type Value struct {
// typ holds the type of the value represented by a Value.
typ *rtype
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer
// flag holds metadata about the value.
// The lowest bits are flag bits:
// - flagStickyRO: obtained via unexported not embedded field, so read-only
// - flagEmbedRO: obtained via unexported embedded field, so read-only
// - flagIndir: val holds a pointer to the data
// - flagAddr: v.CanAddr is true (implies flagIndir)
// - flagMethod: v is a method value.
// The next five bits give the Kind of the value.
// This repeats typ.Kind() except for method values.
// The remaining 23+ bits give a method number for method values.
// If flag.kind() != Func, code can assume that flagMethod is unset.
// If ifaceIndir(typ), code can assume that flagIndir is set.
flag
// A method value represents a curried method invocation
// like r.Read for some receiver r. The typ+val+flag bits describe
// the receiver r, but the flag's Kind bits say Func (methods are
// functions), and the top bits of the flag give the method number
// in r's type's method table.
}
reflect.Value 結構體的字段都是私有的,也就是說,只能使用 reflect.Value 的方法。它有如下常用方法,
| 方法 | 說明 |
|---|---|
Interface() interface {} |
將值以 interface{} 類型返回,可以通過類型斷言轉換為指定類型 |
Int() int64 |
將值以 int 類型返回,所有有符號整型均可以此方式返回 |
Uint() uint64 |
將值以 uint 類型返回,所有無符號整型均可以此方式返回 |
Float() float64 |
將值以雙精度(float64)類型返回,所有浮點數(shù)(float32、float64)均可以此方式返回 |
Bool() bool |
將值以 bool 類型返回 |
Bytes() []bytes |
將值以字節(jié)數(shù)組 []bytes 類型返回 |
String() string |
將值以字符串類型返回 |
CanSet() bool |
是否可以修改對應的值 |
Elem() Type |
獲取指針指向的值,一般用于修改對應的值 |
Kind() Kind |
獲取對應的類型類別,比如Array、Slice、Map等 |
通過反射獲取值
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
}
通過反射設置變量的值
想要在函數(shù)中通過反射修改變量的值,需要注意函數(shù)參數(shù)傳遞的是值拷貝,必須傳遞變量地址才能修改變量值。而反射中使用專有的Elem()方法來獲取指針對應的值。
func main() {
var a int64 = 10
v := reflect.ValueOf(a)
if v.Kind() == reflect.Int64 {
v.SetInt(20) //panic: reflect: reflect.Value.SetInt using unaddressable value
}
}
func main() {
var a int64 = 10
v := reflect.ValueOf(&a) //反射獲取指針的地址
// 反射中使用 Elem()方法獲取指針對應的值
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(20)
}
fmt.Println(a) //20
}
isNil()和isValid()
isNil()
func (v Value) IsNil() bool
IsNil()報告v持有的值是否為nil。v持有的值的分類必須是通道、函數(shù)、接口、映射、指針、切片之一;否則IsNil函數(shù)會導致panic。
isValid()
func (v Value) IsValid() bool
IsValid()返回v是否持有一個值。如果v是Value零值會返回假,此時v除了IsValid、String、Kind之外的方法都會導致panic。
IsNil()常被用于判斷指針是否為空;IsValid()常被用于判定返回值是否有效。
func main() {
// *int類型空指針
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil()) //var a *int IsNil: true
// nil值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid()) //nil IsValid: false
// 實例化一個匿名結構體
b := struct{}{}
// 嘗試從結構體中查找"abc"字段
fmt.Println("b結構體是否存在成員abc:", reflect.ValueOf(b).FieldByName("abc").IsValid()) //b結構體是否存在成員abc: false
// 嘗試從結構體中查找"abc"方法
fmt.Println("b結構體是否存在方法abc:", reflect.ValueOf(b).MethodByName("abc").IsValid()) //b結構體是否存在方法abc: false
// map
c := map[string]int{}
// 嘗試從map中查找一個不存在的鍵
fmt.Println("map中是否存在鍵張三:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("張三")).IsValid()) //map中是否存在鍵張三: false
}
結構體反射
與結構體相關的方法
任意值通過reflect.TypeOf()獲得反射對象信息后,如果它的類型是結構體,可以通過反射值對象(reflect.Type)的NumField()和Field()方法獲得結構體成員的詳細信息。
reflect.Type中與獲取結構體成員相關的的方法如下表所示。
| 方法 | 說明 |
|---|---|
| Field(i int) StructField | 根據索引,返回索引對應的結構體字段的信息。 |
| NumField() int | 返回結構體成員字段數(shù)量。 |
| FieldByName(name string) (StructField, bool) | 根據給定字符串返回字符串對應的結構體字段的信息。 |
| FieldByIndex(index []int) StructField | 多層成員訪問時,根據 []int 提供的每個結構體的字段索引,返回字段的信息。 |
| FieldByNameFunc(match func(string) bool) (StructField,bool) | 根據傳入的匹配函數(shù)匹配需要的字段。 |
| NumMethod() int | 返回該類型的方法集中方法的數(shù)目 |
| Method(int) Method | 返回該類型方法集中的第i個方法 |
| MethodByName(string)(Method, bool) | 根據方法名返回該類型方法集中的方法 |
StructField類型
StructField類型用來描述結構體中的一個字段的信息。
StructField的定義如下:
type StructField struct {
// 參見http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string // Name是字段的名字。
PkgPath string // PkgPath是非導出字段的包路徑,對導出字段該字段為""。
Type Type // 字段的類型
Tag StructTag // 字段的標簽
Offset uintptr // 字段在結構體中的字節(jié)偏移量
Index []int // 用于Type.FieldByIndex時的索引切片
Anonymous bool // 是否匿名字段
}
結構體反射示例
當我們使用反射得到一個結構體數(shù)據之后可以通過索引依次獲取其字段信息,也可以通過字段名去獲取指定的字段信息。
type student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
stu := student{
Name: "張三",
Score: 90,
}
t := reflect.TypeOf(stu)
fmt.Println(t.Name(), t.Kind()) // student struct
// 通過for循環(huán)遍歷結構體的所有字段信息
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
}
// 通過字段名獲取指定結構體字段信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
運行結果:
student struct
name:Name index:[0] type:string json tag:name
name:Score index:[1] type:int json tag:score
name:Score index:[1] type:int json tag:score
接下來編寫一個函數(shù)printMethod(s interface{})來遍歷打印s包含的方法。
func (s student) Study() string {
msg := "學習"
fmt.Println(msg)
return msg
}
func (s student) Sleep() string {
msg := "睡覺"
fmt.Println(msg)
return msg
}
func printMethod(x interface{}) {
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println(t.NumMethod())
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("method name:%s\n", t.Method(i).Name)
fmt.Printf("method:%s\n", methodType)
// 通過反射調用方法傳遞的參數(shù)必須是 []reflect.Value 類型
args := []reflect.Value{}
v.Method(i).Call(args)
}
}
運行結果:
2
method name:Sleep
method:func() string
睡覺
method name:Study
method:func() string
學習
反射的規(guī)則
根據上面對反射的大致介紹,對反射有了一定的了解,其實反射的操作步驟非常的簡單,就是通過實例對象獲取反射對象(Value、Type),然后操作相應的方法
實例、Value、Type 三者之間的轉換關系:

- 從實例到
Value
通過實例獲取 Value 對象,直接使用 reflect.ValueOf() 函數(shù):
func ValueOf(i interface {}) Value
- 從實例到
Type
通過實例獲取反射對象的 Type,直接使用 reflect.TypeOf() 函數(shù):
func TypeOf(i interface{}) Type
- 從
Type到Value
Type 里面只有類型信息,所以直接從一個 Type 接口變量里面是無法獲得實例的 Value 的,但可以通過該 Type 構建一個新實例的 Value。reflect 包提供了兩種方法,示例如下:
//New 返回的是一個 Value,該 Value 的 type 為 PtrTo(typ),即 Value 的 Type 是指定 typ 的指針類型
func New(typ Type) Value
//Zero 返回的是一個 typ 類型的零佳,注意返回的 Value 不能尋址,位不可改變
func Zero(typ Type) Value
如果知道一個類型值的底層存放地址,則還有一個函數(shù)是可以依據 type 和該地址值恢復出 Value 的:
func NewAt(typ Type, p unsafe.Pointer) Value
- 從
Value到Type
從反射對象 Value 到 Type 可以直接調用 Value 的方法,因為 Value 內部存放著到 Type 類型的指針:
func (v Value) Type() Type
- 從
Value到實例
Value 本身就包含類型和值信息,reflect 提供了豐富的方法來實現(xiàn)從 Value 到實例的轉換:
//該方法最通用,用來將 Value 轉換為空接口,該空接口內部存放具體類型實例
//可以使用接口類型查詢去還原為具體的類型
func (v Value) Interface() (i interface{})
//Value 自身也提供豐富的方法,直接將 Value 轉換為簡單類型實例,如果類型不匹配,則直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64
......
- 從
Value的指針到值
從一個指針類型的 Value 獲得值類型 Value 有兩種方法:
//如果 v 類型是接口,則 Elem() 返回接口綁定的實例的 Value,如采 v 類型是指針,則返回指針值的 Value,否則引起 panic
func (v Value) Elem() Value
//如果 v 是指針,則返回指針值的 Value,否則返回 v 自身,該函數(shù)不會引起 panic
func Indirect(v Value) Value
Type指針和值的相互轉換
指針類型 Type 到值類型 Type:
//t 必須是 Array、Chan、Map、Ptr、Slice,否則會引起 panic
//Elem 返回的是其內部元素的 Type
func (t *rtype) Elem() Type
值類型 Type 到指針類型 Type:
//PtrTo 返回的是指向 t 的指針型 Type
func PtrTo(t Type) Type
Value值的可修改性
Value 值的修改涉及如下兩個方法:
//通過 CanSet 判斷是否能修改
func (v Value ) CanSet() bool
//通過 Set 進行修改
func (v Value ) Set(x Value)
實例對象傳遞給接口的是一個完全的值拷貝,如果調用反射的方法 reflect.ValueOf() 傳進去的是一個值類型變量, 則獲得的 Value 實際上是原對象的一個副本,這個 Value 是無論如何也不能被修改的。
反射是計算機語言中程序檢視其自身結構的一種方法,它屬于元編程的一種形式。反射靈活、強大,但也存在不安全。它可以繞過編譯器的很多靜態(tài)檢查,如果過多使用便會造成混亂。為了幫助開發(fā)者更好地理解反射,Go 語言的作者在博客上總結了反射的三大定律。
1.Reflection goes from interface value to reflection object.
2.Reflection goes from reflection object to interface value.
3.To modify a reflection object, the value must be settable.
- 任何接口值 interface{} 都可以反射出反射對象,也就是 reflect.Value 和 reflect.Type,通過函數(shù) reflect.ValueOf 和 reflect.TypeOf 獲得。
- 反射對象也可以還原為 interface{} 變量,也就是第 1 條定律的可逆性,通過 reflect.Value 結構體的 Interface 方法獲得。
- 要修改反射的對象,該值必須可設置,也就是可尋址。
任何類型的變量都可以轉換為空接口 intferface{},所以第 1 條定律中函數(shù) reflect.ValueOf 和 reflect.TypeOf 的參數(shù)就是 interface{},表示可以把任何類型的變量轉換為反射對象。在第 2 條定律中,reflect.Value 結構體的 Interface 方法返回的值也是 interface{},表示可以把反射對象還原為對應的類型變量。
反射的使用
從relfect.Value中獲取接口interface的信息
當執(zhí)行reflect.ValueOf(interface)之后,就得到了一個類型為”relfect.Value”變量,可以通過它本身的Interface()方法獲得接口變量的真實內容,然后可以通過類型判斷進行轉換,轉換為原有真實類型。不過,可能是已知原有類型,也有可能是未知原有類型:
已知原有類型
已知類型后轉換為其對應的類型的做法如下,直接通過Interface方法然后強制轉換,如下:
realValue := value.Interface().(已知的類型)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 可以理解為“強制轉換”,但是需要注意的時候,轉換的時候,如果轉換的類型不完全符合,則直接panic
// Golang 對類型要求非常嚴格,類型一定要完全符合
// 如下兩個,一個是*float64,一個是float64,如果弄混,則會panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
運行結果:
0xc000098000
1.2345
說明
- 轉換的時候,如果轉換的類型不完全符合,則直接panic,類型要求非常嚴格!
- 轉換的時候,要區(qū)分是指針還是值
- 也就是說反射可以將“反射類型對象”再重新轉換為“接口類型變量”
未知原有類型
很多情況下,可能并不知道其具體類型,那么就需要進行遍歷探測其Filed來得知:
type Person struct {
Name string
Age int
Sex string
}
func (p Person) Say(msg string) {
fmt.Println("hello,",msg)
}
func (p Person) PrintInfo() {
fmt.Printf("姓名:%s,年齡:%d,性別:%s\n",p.Name,p.Age,p.Sex)
}
func main() {
p1 := Person{"張三",25,"男"}
DoFiledAndMethod(p1)
}
// 通過接口來獲取任意參數(shù)
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input) //先獲取input的類型
fmt.Println("get Type is :", getType.Name()) // Person
fmt.Println("get Kind is : ", getType.Kind()) // struct
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue) //{張三 25 男}
// 獲取方法字段
// 1. 先獲取interface的reflect.Type,然后通過NumField進行遍歷
// 2. 再通過reflect.Type的Field獲取其Field
// 3. 最后通過Field的Interface()得到對應的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface() //獲取第i個值
fmt.Printf("字段名稱:%s, 字段類型:%s, 字段數(shù)值:%v \n", field.Name, field.Type, value)
}
// 通過反射,操作方法
// 1. 先獲取interface的reflect.Type,然后通過.NumMethod進行遍歷
// 2. 再公國reflect.Type的Method獲取其Method
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法名稱:%s, 方法類型:%v \n", method.Name, method.Type)
}
}
運行結果:
get Type is : Person
get Kind is : struct
get all Fields is: {張三 25 男}
字段名稱:Name, 字段類型:string, 字段數(shù)值:張三
字段名稱:Age, 字段類型:int, 字段數(shù)值:25
字段名稱:Sex, 字段類型:string, 字段數(shù)值:男
方法名稱:PrintInfo, 方法類型:func(main.Person)
方法名稱:Say, 方法類型:func(main.Person, string)
說明
通過運行結果可以得知獲取未知類型的interface的具體變量及其類型的步驟為:
- 先獲取interface的reflect.Type,然后通過NumField進行遍歷
- 再通過reflect.Type的Field獲取其Field
- 最后通過Field的Interface()得到對應的value
通過運行結果可以得知獲取未知類型的interface的所屬方法(函數(shù))的步驟為:
- 先獲取interface的reflect.Type,然后通過NumMethod進行遍歷
- 再分別通過reflect.Type的Method獲取對應的真實的方法(函數(shù))
- 最后對結果取其Name和Type得知具體的方法名
- 也就是說反射可以將“反射類型對象”再重新轉換為“接口類型變量”
- struct 或者 struct 的嵌套都是一樣的判斷處理方式
如果是struct的話,可以使用Elem()
tag := t.Elem().Field(0).Tag //獲取定義在struct里面的Tag屬性
name := v.Elem().Field(0).String() //獲取存儲在第一個字段里面的值
通過reflect.Value設置實際變量的值
reflect.Value是通過reflect.ValueOf(X)獲得的,只有當X是指針的時候,才可以通過reflec.Value修改實際變量X的值,即:要修改反射類型的對象就一定要保證其值是可尋址的。
這里需要一個方法:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value
解釋起來就是:Elem返回接口v包含的值或指針v指向的值。如果v的類型不是interface或ptr,它會恐慌。如果v為零,則返回零值。
func main() {
var num float64 = 1.2345
fmt.Println("old value of pointer:", num)
// 通過reflect.ValueOf獲取num中的reflect.Value,注意,參數(shù)必須是指針才能修改其值
pointer := reflect.ValueOf(&num)
newValue := pointer.Elem()
fmt.Println("type of pointer:", newValue.Type())
fmt.Println("settability of pointer:", newValue.CanSet())
// 重新賦值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num)
////////////////////
// 如果reflect.ValueOf的參數(shù)不是指針,會如何?
//pointer = reflect.ValueOf(num)
//newValue = pointer.Elem() // 如果非指針,這里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}
運行結果:
old value of pointer: 1.2345
type of pointer: float64
settability of pointer: true
new value of pointer: 77
說明
- 需要傳入的參數(shù)是* float64這個指針,然后可以通過pointer.Elem()去獲取所指向的Value,注意一定要是指針。
- 如果傳入的參數(shù)不是指針,而是變量,那么
- 通過Elem獲取原始值對應的對象則直接panic
- 通過CanSet方法查詢是否可以設置返回false
- newValue.CantSet()表示是否可以重新設置其值,如果輸出的是true則可修改,否則不能修改,修改完之后再進行打印發(fā)現(xiàn)真的已經修改了。
- reflect.Value.Elem() 表示獲取原始值對應的反射對象,只有原始對象才能修改,當前反射對象是不能修改的
- 也就是說如果要修改反射類型對象,其值必須是可尋址的【對應的要傳入的是指針,同時要通過Elem方法獲取原始值對應的反射對象】
- struct 或者 struct 的嵌套都是一樣的判斷處理方式
通過reflect.Value來進行方法的調用
在項目應用中,另外一個常用并且屬于高級的用法,就是通過reflect來進行方法的調用。比如要做框架工程的時候,需要可以隨意擴展方法,或者說用戶可以自定義方法,關鍵點在于用戶的自定義方法是未可知的,因此可以通過reflect來搞定。
Call()方法:
// Call calls the function v with the input arguments in.
// For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
// Call panics if v's Kind is not Func.
// It returns the output results as Values.
// As in Go, each input argument must be assignable to the
// type of the function's corresponding input parameter.
// If v is a variadic function, Call creates the variadic slice parameter
// itself, copying in the corresponding values.
func (v Value) Call(in []Value) []Value
通過反射,調用方法。
type Person struct {
Name string
Age int
Sex string
}
func (p Person) Say(msg string) {
fmt.Println("hello,",msg)
}
func (p Person) PrintInfo() {
fmt.Printf("姓名:%s,年齡:%d,性別:%s\n",p.Name,p.Age,p.Sex)
}
func (p Person) Test(i,j int,s string){
fmt.Println(i,j,s)
}
// 如何通過反射來進行方法的調用?
// 本來可以用結構體對象.方法名稱()直接調用的,
// 但是如果要通過反射,
// 那么首先要將方法注冊,也就是MethodByName,然后通過反射調動mv.Call
func main() {
p2 := Person{"張三",25,"男"}
// 1. 要通過反射來調用起對應的方法,必須要先通過reflect.ValueOf(interface)來獲取到reflect.Value,
// 得到“反射類型對象”后才能做下一步處理
getValue := reflect.ValueOf(p2)
// 2.一定要指定參數(shù)為正確的方法名
// 先看看沒有參數(shù)的調用方法
methodValue1 := getValue.MethodByName("PrintInfo")
fmt.Printf("Kind : %s, Type : %s\n",methodValue1.Kind(),methodValue1.Type())
methodValue1.Call(nil) //沒有參數(shù),直接寫nil
args1 := make([]reflect.Value, 0) //或者創(chuàng)建一個空的切片也可以
methodValue1.Call(args1)
// 有參數(shù)的方法調用
methodValue2 := getValue.MethodByName("Say")
fmt.Printf("Kind : %s, Type : %s\n",methodValue2.Kind(),methodValue2.Type())
args2 := []reflect.Value{reflect.ValueOf("反射機制")}
methodValue2.Call(args2)
methodValue3 := getValue.MethodByName("Test")
fmt.Printf("Kind : %s, Type : %s\n",methodValue3.Kind(),methodValue3.Type())
args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200),reflect.ValueOf("Hello")}
methodValue3.Call(args3)
}
運行結果:
Kind : func, Type : func()
姓名:張三,年齡:25,性別:男
姓名:張三,年齡:25,性別:男
Kind : func, Type : func(string)
hello, 反射機制
Kind : func, Type : func(int, int, string)
100 200 Hello
通過反射,調用函數(shù)。
函數(shù)像普通的變量一樣,是可以把函數(shù)作為一種變量類型的,而且是引用類型。如果說Fun()是一個函數(shù),那么f1 := Fun也是可以的,那么f1也是一個函數(shù),如果直接調用f1(),那么運行的就是Fun()函數(shù)。
那么就先通過ValueOf()來獲取函數(shù)的反射對象,可以判斷它的Kind,是一個func,那么就可以執(zhí)行Call()進行函數(shù)的調用。
func main() {
//函數(shù)的反射
f1 := fun1
value := reflect.ValueOf(f1)
fmt.Printf("Kind : %s , Type : %s\n",value.Kind(),value.Type()) //Kind : func , Type : func()
value2 := reflect.ValueOf(fun2)
fmt.Printf("Kind : %s , Type : %s\n",value2.Kind(),value2.Type()) //Kind : func , Type : func(int, string)
//通過反射調用函數(shù)
value.Call(nil)
value2.Call([]reflect.Value{reflect.ValueOf(100),reflect.ValueOf("hello")})
}
func fun1(){
fmt.Println("函數(shù)fun1(),無參。。")
}
func fun2(i int, s string){
fmt.Println("函數(shù)fun2(),有參數(shù)。。",i,s)
}
說明
- 要通過反射來調用起對應的方法,必須要先通過reflect.ValueOf(interface)來獲取到reflect.Value,得到“反射類型對象”后才能做下一步處理
- reflect.Value.MethodByName這個MethodByName,需要指定準確真實的方法名字,如果錯誤將直接panic,MethodByName返回一個函數(shù)值對應的reflect.Value方法的名字。
- []reflect.Value,這個是最終需要調用的方法的參數(shù),可以沒有或者一個或者多個,根據實際參數(shù)來定。
- reflect.Value的 Call 這個方法,這個方法將最終調用真實的方法,參數(shù)務必保持一致,如果reflect.Value.Kind不是一個方法,那么將直接panic。
- 本來可以用對象訪問方法直接調用的,但是如果要通過反射,那么首先要將方法注冊,也就是MethodByName,然后通過反射調用methodValue.Call
反射是把雙刃劍
反射是一個強大并富有表現(xiàn)力的工具,能寫出更靈活的代碼。但是反射不應該被濫用,原因有以下三個。
- 基于反射的代碼是極其脆弱的,反射中的類型錯誤會在真正運行的時候才會引發(fā)panic,那很可能是在代碼寫完的很長時間之后。
- 大量使用反射的代碼通常難以理解,代碼可讀性差。
- 反射的性能低下,基于反射實現(xiàn)的代碼通常比正常代碼運行速度慢一到兩個數(shù)量級。處于運行效率關鍵位置的代碼,請避免使用反射。