参考:|--http://blog.51cto.com/speakingbaicai/1707637

   |--https://studygolang.com/articles/6324

  反射是在golang程序运行时检查变量所具有类型的一种机制。由于反射可以得出关于变量结构数据(即“关于数据的数据”),所以这也被认为是golang元编程的基础。我们由反射三法则入手:

从类型和方法理解反射内涵

  在基本的层面上,反射只是一个检查存储在接口变量中的类型和值的算法。使用反射机制,首先需要导入reflect包,reflect包中有两个重要类型需要了解,reflect.Type和reflect.Value,这两个类型使得可以访问变量的内容。与此相关的,还有两个简单的函数,reflect.TypeOf和reflect.ValueOf,可以从接口值中分别获取reflect.Type和reflect.Value。

     初学可能会认为reflect.Type和reflect.Value是一种并列关系,但其实它们是一种包含关系,我们结合一段代码来理解这段话。

import (
"fmt"
"reflect"
) func main() {
var x float64 = 1.1
fmt.Println("reflect.Value:", reflect.ValueOf(x))
fmt.Println("reflect.Type:", reflect.TypeOf(x))
v := reflect.ValueOf(x)
fmt.Println("reflect.Type:",v.Type())
fmt.Println("actual value:", v.Float())
fmt.Println("kind is float64?", v.Kind() == reflect.Float64)
}

根据程序及其结果,我们可以发现:在go语言中,每个值都包含两个内容:类型和实际的值。从类型角度来看,reflect.Value是一个关于<类型, 实际的值>的二元组,而reflect.Type是值的类型,二者是包含关系。从方法角度来看,reflect.TypeOf 和 (reflect.ValueOf(x)).Type都可以返回reflect.Type;(reflect.ValueOf(x)).Float可以返回实际的值(类似的方法还包括(reflect.ValueOf(x)).Int、(reflect.ValueOf(x)).Bool等);(reflect.ValueOf(x)).Kind可以返回一个常量定义的类型。

根据上述分析,我们可以得出一个示意图,更为直观形象的表明值、类型、实际的值的关系。

此外,golang采用静态类型机制,TypeOf返回静态类型;但是,Kind返回底层类型。我们同样以一段代码来验证这段话。

import (
"fmt"
"reflect"
) type MyInt int func main() {
var x MyInt = 1
v := reflect.ValueOf(x)
fmt.Println("reflect.Type:", v.Type())
fmt.Println("kind is int?", v.Kind() == reflect.Int)
}

反射三法则:

1 法则一:从接口值到反射对象的反射(Reflection goes from interface value toreflection object)

前文所述内容其实就是从接口值到反射对象的反射,代表方法为reflect.ValueOf和reflect.TypeOf。可能有人会问,接口?接口在哪呢?我们来看一些前文提到这两个函数的声明,函数的参数是空接口,其实接口就在那里。

func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type

2 法则二:从反射对象到接口值的反射(Reflection goes from reflection object to interface value)

从reflect.Value可以使用Interface方法还原接口值;此方法可以高效地打包类型和值信息到接口表达中,并返回这个结果。方法声明:

func (v Value) Interface() interface{}

通过反射对象 v 可以打印 float64 的表达值。

y :=v.Interface().(float64) // y 将为类型 float64。
fmt.Println(y)

还有更为简洁的实现。fmt.Println,fmt.Printf等其他所有传递一个空接口值作为参数的函数,在 fmt包内部解包的方式就像之前的例子这样。因此正确的打印reflect.Value的内容的方法就是将Interface方法的结果进行格式化打印(formatted print routine).

fmt.Println(v.Interface())

为什么不是fmt.Println(v)?因为v是一个 reflect.Value;这里希望获得的是它保存的实际的值。

我们修改前文代码还进行验证:

func main() {
var x float64 = 1.1
fmt.Println("reflect.Value:", reflect.ValueOf(x))
fmt.Println("reflect.Type:", reflect.TypeOf(x))
v := (reflect.ValueOf(x))
fmt.Println("reflect.Type:", v.Type())
fmt.Println("actual value(interface):", v.Interface())
fmt.Println("kind is float64?", v.Kind() == reflect.Float64)
}

其输出:

进一步地,我们可以修改上述关系示意图,新图更为简洁优雅:

3. 为了修改反射对象,其值必须可设置(To modify a reflectionobject, the value must be settable)

反射对象可以通过SetFloat等方法设置值,通过CanSet判断可设置性。但是这里面有坑,有些值是不可设置的。我们还是通过一段代码来看。

import (
"fmt"
"reflect"
) func main() {
var x float64 = 1.1
v := reflect.ValueOf(x)
fmt.Println("settability of v:",v.CanSet())
v.SetFloat(1.2)
}

 

其结果表明,反射对象v是不可设置的,如果硬要设置的话,会有panic异常。

为什么不能设置呢?我们可以从函数传参的角度来思考这个问题。V := reflect.ValueOf(x),这个函数是值传递,即传递了一个x的副本到函数中,而非x本身。我们都知道,值传递的参数是不能被真正修改的。

我最初还有过这样的想法:v和x又不是一个变量,x不能被修改,但是v应该可以被修改啊。完全从形式上考虑,这样似乎有道理。但是再多想一层,如果允许执行,虽然v可以被修改,但是却无法更新x。也就是说,在反射值内部允许修改x的副本,但是x本身却不会受到这个影响。这会造成混乱,并且毫无意义,因此在golang中这样操作是非法的。

让我们重新用函数传参的角度思考这个问题。如果传递副本不能修改,那我们就通过就传递指针好了。我们来试试:

func main() {
var x float64 = 1.1
p := reflect.ValueOf(&x)
fmt.Println("type of p:",p.Type())
fmt.Println("settability of p:",p.CanSet())
}

  

  还是不行。因为p的实际类型是*float64,而非float64,这样修改相当于要直接修改地址了。

  我们可以借助Elem方法,通过指针来修改指针指向的具体值。

func (v Value)Elem() Value
func main() {
var x float64 = 1.1
p := reflect.ValueOf(&x)
fmt.Println("type of p:",p.Type())
v := p.Elem()
fmt.Println("type of v:",v.Type())
fmt.Println("settability of v:",v.CanSet())
}

 

这样就可以进行修改了。虽然p是不可修改的,但是v可以修改。这种方法思路上类似引用传参,传入地址,修改地址所指向的具体值。

 

golang 反射的更多相关文章

  1. golang反射初试

    golang反射来自Go AST(Abstract Syntax Tree). reflect操作更多像traverse AST. t := reflect.TypeOf(obj) 使用TypeOf( ...

  2. golang 反射应用(二)

    golang反射应用(二) package test import ( "reflect" "testing" ) //定义适配器 func TestRefle ...

  3. Golang反射机制

    Go反射机制:在编译不知道类型的情况下,可更新变量.在运行时查看值.调用方法以及直接对它们的布局进行操作. 为什么使用反射 有时需要封装统一接口对不同类型数据做处理,而这些类型可能无法共享同一个接口, ...

  4. [golang]反射的用处--代码自动生成

    背景: go语言处理db.json的时候,具体代码的变量定义和db字段.json输出的时候可能不一样. 这个时候,我们需要用tag的方式来进行定义. 例如: type MyStruct struct ...

  5. GOLANG 反射法则

    译自[blog.golang.org/laws-of-reflection] 在计算机中, 反射是程序通过类型,检测到它自己的结构能力:是一种元编程程:也是一个具大的混淆点 在本文中,我们将通过解释反 ...

  6. golang 反射中调用方法

    反射中调用函数 众所周知,golang中的函数是可以像普通的int.float等类型变量那样作为值的,例如: package main import "fmt" func hell ...

  7. GO开发[六]:golang反射(reflect)

    反射 反射:可以在运行时动态获取变量的相关信息 ​ Import ("reflect") reflect.TypeOf,获取变量的类型,返回reflect.Type类型 refle ...

  8. golang反射

    要点 1.变量 2.反射 3.结构体反射 4.反射总结以及应用场景 一.变量介绍 1.变量的内在机制 A.类型信息,这部分是元信息,是预定义好的 B.值类型,这部分是程序运行过程中,动态改变的 var ...

  9. golang反射举例

    反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:并且能改变它的属性. package main import ( "f ...

随机推荐

  1. day91 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件   本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...

  2. ruby puts, print, p方法比较

    1.puts([obj[, obj2[, ....]]] ) 依次将obj和换行符输出到$>.若没有参数的话则只会输出换行符. 若参数是数组,则依次输出数组元素和换行符.若将既非数组又非字符串的 ...

  3. SEO优化上首页之搜索引擎原理内容处理与索引

    上文<搜索引擎原理SEO优化上首页之蜘蛛Spider>详细介绍了蜘蛛的分类.抓取入口.抓取策略和更新策略.搜索引擎已把页面抓取回来,接下来是解析页面内容,主要包含判断页面类型.提取页面主题 ...

  4. 20145234黄斐《Java程序设计》MyDC

    http://git.oschina.net/jiataiji/java

  5. EDB*Plus的当前路径问题

    磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面: PostgreSQL基础知识与基本操作索引页     回到顶级页面:PostgreSQL索引页 [作者 高健@博客园  luckyjackg ...

  6. 9 stark组件 增删改

    1.效果图 2.详细步骤解析 1.构造增删改查url,反向解析 2.ModelForm定制add.edit页面 3.staradmin中的ModelForm 3.总结.代码 1.知识点 1.解决代码重 ...

  7. 27-ATM+购物车程序

    1.需求 本章作业: 模拟实现一个ATM + 购物商城程序 额度 15000或自定义 实现购物商城,买东西加入 购物车,调用信用卡接口结账 可以提现,手续费5% 支持多账户登录 支持账户间转账 记录每 ...

  8. [CQOI2012]组装 贪心

    [CQOI2012]组装 贪心好题. LG传送门 首先有一个必须要能推的式子:设第\(i\)种零件选的生产车间位置为\(x _ i\),组装车间位置为\(x\), 则总的花费为 \[f(x) = \s ...

  9. 2_C语言中的数据类型 (六)浮点数

    1.1       浮点float,double,long double类型 1.1.1          浮点常量,变量 Float在32位系统下是4个字节,double在32位系统下是8个字节 小 ...

  10. paramiko 简单的使用

    感觉自己操作服务器还要用xshell,麻烦很多,于是呢就去google,找到了paramiko. 使用这个模块还是很简单的, 我们链接服务器,只需要导入 SSHClient,AutoAddPolicy ...