go语言快速入门:go的反射机制(22)

liumiaocn · · 1309 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

版权声明:本文为博主原创文章,未经博主允许欢迎转载,但请注明出处。 https://blog.csdn.net/liumiaocn/article/details/55253632

Java的反射机制是其标志性的特征之一,正是这种语言本身支持的强大的机制使得很多流行的框架有了用武之地。C++中虽然也能实现,但是语言本身并没有提供标准的支持。
而作为一门现代的语言,go语言也引入了反射机制,在这篇文章中我们将会了解一下go语言中的反射机制是如何使用的。

反射机制

反射机制是程序能够检查其自身结构,属于元编程的范畴,强大的同时也往往是困扰的源头。虽然各种语言的反射模型有所不同,但是通过简单的比较也能有所收获。在了解Go的反射机制之前先来看看Java的反射机制吧。

Java的反射机制

我们所熟知的Java的反射机制是什么?对于类和对象的使用,普通的方式是知道类和对象的属性和方法之后进行调用或者访问。
而反射机制,简单来说,是在运行状态中,Java对于任何的类,都能够确认到这个类的所有方法和属性;对于任何一个对象,都能调用它的任意方法和属性。这种动态获取或者调用的方式就是Java的反射机制。

能做什么

在Java中,通过反射机制在运行时能够做到如下:

  • 确认对象的类
  • 确认类的所有成员变量和方法
  • 动态调用任意一个对象的方法

相关基础

在进行更加详细的了解之前,我们需要重新温习一下Go语言相关的一些特性,所谓温故知新,从这些特性中了解其反射机制是如何使用的。

特点 说明
go语言是静态类型语言。 编译时类型已经确定,比如对已基本数据类型的再定义后的类型,反射时候需要确认返回的是何种类型。
空接口interface{} go的反射机制是要通过接口来进行的,而类似于Java的Object的空接口可以和任何类型进行交互,因此对基本数据类型等的反射也直接利用了这一特点

反射标准库

反射是使用库reflect来实现,使用的时候需要将reflect库import进来

项目 详细
反射标准库 reflect
文档 https://golang.org/pkg/reflect/
反射类型 reflect.Type 和 reflect.Value
常用函数 reflect.TypeOf 和 reflect.ValueOf

reflect.TypeOf 和 reflect.ValueOf的定义如下:

[root@liumiaocn goprj]# go doc reflect.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.

[root@liumiaocn goprj]# go doc reflect.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.

[root@liumiaocn goprj]#

反射定律

go语言使用时,有三条定律,简单整理如下

项番 详细 E文
No.1 反射可以将“接口类型变量”转换为“反射类型对象”。 Reflection goes from interface value to reflection object.
No.2 反射可以将“反射类型对象”转换为“接口类型变量”。 Reflection goes from reflection object to interface value.
No.3 如果要修改“反射类型对象”,其值必须是“可写的”。 To modify a reflection object, the value must be settable.

下面通过一些详细的例子来说明这三条定律

No.1: “接口类型变量”=>“反射类型对象”

使用例子1:

[root@liumiaocn goprj]# cat basic-reflect-1.go
package main

import . "fmt"
import "reflect"

func main() {
        var circle float64 = 6.28
        var icir interface{}

        icir = circle
        Println("Reflect : circle.Value = ", reflect.ValueOf(icir))
        Println("Reflect : circle.Type  = ", reflect.TypeOf(icir))
}
[root@liumiaocn goprj]#

执行结果:

[root@liumiaocn goprj]# go run basic-reflect-1.go
Reflect : circle.Value =  6.28
Reflect : circle.Type  =  float64
[root@liumiaocn goprj]#

可以看到ValueOf和TypeOf的参数都是空接口,因此,这说明可以直接使用变量传进取,比如:

[root@liumiaocn goprj]# cat basic-reflect-2.go
package main

import . "fmt"
import "reflect"

func main() {
        var circle float64 = 6.28

        Println("Reflect : circle.Value = ", reflect.ValueOf(circle))
        Println("Reflect : circle.Type  = ", reflect.TypeOf(circle))
}
[root@liumiaocn goprj]#

执行结果:

[root@liumiaocn goprj]# go run basic-reflect-2.go
Reflect : circle.Value =  6.28
Reflect : circle.Type  =  float64
[root@liumiaocn goprj]#

No.2: “反射类型对象”=>“接口类型变量

此为No.1的反向过程,简单例子代码如下:

[root@liumiaocn goprj]# cat basic-reflect-3.go
package main

import . "fmt"
import "reflect"

func main() {
        var circle float64 = 6.28
        var icir interface{}

        icir = circle
        Println("Reflect : circle.Value = ", reflect.ValueOf(icir))
        Println("Reflect : circle.Type  = ", reflect.TypeOf(icir))

        valueref := reflect.ValueOf(icir)
        Println(valueref)
        Println(valueref.Interface())

        y := valueref.Interface().(float64)
        Println(y)
}
[root@liumiaocn goprj]#

执行结果

[root@liumiaocn goprj]# go run basic-reflect-3.go
Reflect : circle.Value =  6.28
Reflect : circle.Type  =  float64
6.28
6.28
6.28
[root@liumiaocn goprj]#

No.3: 修改“反射类型对象”

第三条定律不容易理解,让我们从一个简单的错误开始, 代码如下:

[root@liumiaocn goprj]# cat basic-reflect-4.go
package main

import . "fmt"
import "reflect"

func main() {
        var circle float64 = 6.28

        value := reflect.ValueOf(circle)
        Println("Reflect : value = ", value)
        value.SetFloat(3.14)
}
[root@liumiaocn goprj]#

执行结果

[root@liumiaocn goprj]# go run basic-reflect-4.go
Reflect : value =  6.28
panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:
panic(0x48a5c0, 0xc42000a2e0)
        /usr/local/go/src/runtime/panic.go:500 +0x1a1
reflect.flag.mustBeAssignable(0x8e)
        /usr/local/go/src/reflect/value.go:228 +0x102
reflect.Value.SetFloat(0x489c40, 0xc42000a2c0, 0x8e, 0x40091eb851eb851f)
        /usr/local/go/src/reflect/value.go:1388 +0x2f
main.main()
        /tmp/goprj/basic-reflect-4.go:11 +0x1ae
exit status 2
[root@liumiaocn goprj]#

通过go doc reflect.Value我们可以看到func (v Value) SetFloat(x float64)是可行的,但是为什么会出错呢?其实仔细想一下就能大体知道原因,value变量之所以是不可写的,因为其所指向的是一个副本,因此不具有可写性,因此找到“本尊”是非常重要的,不过go中和特意提供了一个CanSet函数可以进行确认是否是settable的,稍微有些麻烦,使用如下方式即可进行设定:

[root@liumiaocn goprj]# cat basic-reflect-5.go
package main

import . "fmt"
import "reflect"

func main() {
        var circle float64 = 6.28

        value := reflect.ValueOf(circle)
        Println("Reflect : value = ", value)
        Println("Settability of value : ", value.CanSet())

        value2 := reflect.ValueOf(&circle)
        Println("Settability of value : ", value2.CanSet())

        value3 := value2.Elem()
        Println("Settability of value : ", value3.CanSet())

        value3.SetFloat(3.14)
        Println("Value of value3: ", value3)
        Println("value of circle: ", circle)
}
[root@liumiaocn goprj]#

执行结果

[root@liumiaocn goprj]# go run basic-reflect-5.go
Reflect : value =  6.28
Settability of value :  false
Settability of value :  false
Settability of value :  true
Value of value3:  3.14
value of circle:  3.14
[root@liumiaocn goprj]#

总结

从DB的ClassForname到Spring的IoC,到处都是体现着反射机制的广泛应用,而现在这一切,在go语言中也提供了类似的标准支持,这意味着此种特性也将大有作为。

参考

https://blog.golang.org/laws-of-reflection
https://golang.org/pkg/reflect/


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:liumiaocn

查看原文:go语言快速入门:go的反射机制(22)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1309 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传