1. 程式人生 > >Go語言的反射三定律

Go語言的反射三定律

這裡先丟擲GO語言的反射三定律,然後一一進行講解:

1、反射可以將“介面型別變數”轉換為“反射型別物件”

2、反射可以將“反射型別物件”轉換為“介面型別變數”

3、如果要修改反射型別物件,其值必須是“addressable”

談到Go的反射,涉及到如下幾個概念。

(1)資料型別。go語言中的資料型別有:

Bool            
Int            
Int8            
Int16            
Int32            
Int64            
Uint            
Uint8            
Uint16            
Uint32            
Uint64            
Uintptr            
Float32            
Float64            
Complex64            
Complex128            
Array            
Chan            
Func            
Interface            
Map            
Ptr            
Slice            
String            
Struct            
UnsafePointer

(2)介面型別

介面型別,一種特殊的資料型別,包括一組方法集,同時,其內部維護兩個屬性:value和type。

value指的是實現了介面型別的型別值;type則是對相應型別值的型別描述。

舉例說明:

var i interface{}  //定義空介面變數i,值為nil
var a integer = 10  //定義integer型別變數a
i = a  //因為任何型別都實現了空介面型別interface{},所以這裡的賦值沒問題

此時,介面變數i中就包含屬性(10,integer)

(3)反射

go中的反射是通過reflect包實現的。通過反射機制,可以獲取介面變數儲存的型別以及相應的值。

reflect包定義了兩種反射型別:Type和Value

Type is the representation of a Go type.

Value is the reflection interface to a Go value.

反射定律一:反射可以將“介面型別變數”轉換為“反射型別物件”

注意:這裡的反射型別指的是reflect.Type和reflect.Value

將介面型別變數轉換為反射型別變數,是通過reflect包的TypeOf和ValueOf實現的。


func TypeOf
func TypeOf(i interface{}) Type
TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.

func ValueOf
func ValueOf(i interface{}) Value
ValueOf returns a new Value initialized to the concrete value stored in the interface i. ValueOf(nil) returns the zero Value.

舉例:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var a int = 30
    v := reflect.ValueOf(a)//返回Value型別物件,值為30
    t := reflect.TypeOf(a)//返回Type型別物件,值為int
    fmt.Println(v)
    fmt.Println(t)
    v = reflect.ValueOf(&a)//返回Value型別物件,值為&a,變數a的地址
    t = reflect.TypeOf(&a)//返回Type型別物件,值為*int
    fmt.Println(v)
    fmt.Println(t)
}

執行結果:


C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

30

int

0xc04203a1d0

*int

成功: 程序退出程式碼 0.


上面的案例通過使用reflect.ValueOf和reflect.TypeOf將介面型別變數分別轉換為反射型別物件v和t。

v中包含了介面中的實際值。

t中包含了介面中的實際型別。

大家可能對上面的案例感到疑惑,程式裡沒有介面型別變數啊,哪來的介面型別變數到反射型別物件的轉換啊?

事實上,reflect.ValueOf和reflect.TypeOf的引數型別都是interface{},空介面型別,而返回值的型別是reflect.Value和reflect.Type,中間的轉換由reflect包來實現。

反射定律二:反射可以將“反射型別物件”轉換為“介面型別變數”

根據一個 reflect.Value 型別的變數,我們可以使用 Interface 方法恢復其介面型別的值。事實上,這個方法會把 type 和 value 資訊打包並填充到一個介面變數中,然後返回。


func (Value) Interface
func (v Value) Interface() (i interface{})
Interface returns v's current value as an interface{}. It is equivalent to:

var i interface{} = (v's underlying value)
It panics if the Value was obtained by accessing unexported struct fields.

舉例:

package main
import (
    "fmt"
    "reflect"
)
func main() {
    var a int = 30
    v := reflect.ValueOf(&a) //返回Value型別物件,值為&a,變數a的地址
    t := reflect.TypeOf(&a)  //返回Type型別物件,值為*int
    fmt.Println(v)
    fmt.Println(t)
    v1 := v.Interface() //返回空介面變數
    v2 := v1.(*int)     //型別斷言,斷定v1中type=*int
    fmt.Printf("%T %v\n", v2, v2)
    fmt.Println(*v2)
}

執行:


C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

0xc042046130

*int

*int 0xc042046130

30

成功: 程序退出程式碼 0.

反射定律三:如果要修改反射型別物件,其值必須是“addressable”
通過反射定義一可以知道,反射物件包含了介面變數中儲存的值以及型別。

如果反射物件中包含的值是原始值,那麼可以通過反射物件修改原始值;

如果反射物件中包含的值不是原始值(反射物件包含的是副本值或指向原始值的地址),那麼該反射物件是不可以修改的。

通過CanSet函式可以判定反射物件是否可以修改。


func (Value) CanSet
func (v Value) CanSet() bool
CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. If CanSet returns false, calling Set or any type-specific setter (e.g., SetBool, SetInt) will panic.

舉例:

package main
import (
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    v.SetFloat(7.1) // Error: will panic.
}
執行:

C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:

panic(0x470400, 0xc042006150)

c:/go/src/runtime/panic.go:500 +0x1af

reflect.flag.mustBeAssignable(0x8e)

c:/go/src/reflect/value.go:228 +0x109

reflect.Value.SetFloat(0x46fa40, 0xc042006148, 0x8e, 0x401c666666666666)

c:/go/src/reflect/value.go:1388 +0x36

main.main()

E:/project/go/proj/src/test/test.go:10 +0xa1

exit status 2

錯誤: 程序退出程式碼 1.


舉例:


package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    fmt.Println("settability of v:", v.CanSet())
}
執行:

C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

settability of v: false

成功: 程序退出程式碼 0.


上面的反射物件v不可以修改,是因為v中儲存的是3.4的副本。

舉例:


package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    fmt.Println("settability of v:", v.CanSet())
}
執行:

C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

settability of v: false

成功: 程序退出程式碼 0.

上面的反射物件v不可以修改,是因為v當前儲存的是x的地址,而不是x的原始空間。


舉例:


package main
import (
    "fmt"
    "reflect"
)
func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x).Elem()
    fmt.Println("settability of v:", v.CanSet())
    v.SetFloat(6.6)
    fmt.Println("x=", x)
}
執行:

C:/go/bin/go.exe run test.go [E:/project/go/proj/src/test]

settability of v: true

x= 6.6

成功: 程序退出程式碼 0.


從執行結果,可以看出,這時候的反射物件v是可以修改的,v的修改等同於x的修改。

上面的程式碼裡,出現了Elem函式,Elem用來獲取原始值對應的反射物件。


func (Value) Elem
func (v Value) Elem() Value
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.


關於go的反射機制,就說這麼多吧。

原文地址:http://www.jb51.net/article/90021.htm