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

猜你喜欢

转载自blog.csdn.net/weixin_42654444/article/details/83316115