github:https://github.com/ZhangzheBJUT/blog/blob/master/reflect.md

一 反射的规则

反射是程序执行时检查其所拥有的结构。尤其是类型的一种能力。这是元编程的一种形式。它同一时候也是造成混淆的重要来源。

每一个语言的反射模型都不同(同一时候很多语言根本不支持反射)。本节将试图明白解释在 Go 中的反射是怎样工作的。

1. 从接口值到反射对象的反射

在主要的层面上。反射仅仅是一个检查存储在接口变量中的类型和值的算法。

在 reflect 包中有两个类型须要了解:Type 和 Value。这两个类型使得能够訪问接口变量的内容。还有两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,从接口值中分别获取 reflect.Type
和 reflect.Value。

(注:从 reflect.Value 也非常easy可以获得 reflect.Type,只是这里让 Value 和 Type 在概念上是分离的)

从 TypeOf 開始:

package main

import (
"fmt"
"reflect"
) func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}

这个程序打印 type: float64 



接口在哪里呢,读者可能会对此有疑虑,看起来程序传递了一个 float64 类型的变量 x。而不是一个接口值,到 reflect.TypeOf。可是,它确实就在那里:如同 godoc 报告的那样,reflect.TypeOf 的声明包括了空接口:

// TypeOf 返回 interface{} 中的值反射的类型。

func TypeOf(i interface{}) Type

当调用 reflect.TypeOf(x) 的时候,x 首先存储于一个作为參数传递的空接口中;reflect.TypeOf 解包这个空接口来还原类型信息。

reflect.ValueOf 函数。当然就是还原那个值(从这里開始将会略过那些概念演示样例,而聚焦于可运行的代码):

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))

打印

value: <float64 Value>

除了reflect.Type 和 reflect.Value外,都有很多方法用于检查和操作它们。一个重要的样例是 Value 有一个 Type 方法返回 reflect.Value 的 Type。还有一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。相同 Value 有叫做 Int 和 Float 的方法能够获取存储在内部的值(跟 int64 和 float64 一样):

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

打印

type: float64
kind is float64: true
value: 3.4

同一时候也有类似 SetInt 和 SetFloat 的方法。只是在使用它们之前须要理解可设置性,这部分的主题在以下的第三条军规中讨论。

反射库有着若干特性值得特别说明。

  • 为了保持 API 的简洁,“获取者”和“设置者”用 Value 的最宽泛的类型来处理值:比如,int64 可用于全部带符号整数。也就是说 Value 的 Int 方法返回一个 int64。而 SetInt 值接受一个 int64;所以可能必须转换到实际的类型:

      var x uint8 = 'x'
    v := reflect.ValueOf(x)
    fmt.Println("type:", v.Type()) // uint8.
    fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
    x = uint8(v.Uint()) // v.Uint 返回一个 uint64.
  • 反射对象的 Kind 描写叙述了底层类型。而不是静态类型。假设一个反射对象包括了用户定义的整数类型的值,就像

      type MyInt int
    var x MyInt = 7
    v := reflect.ValueOf(x)‘

v 的 Kind 仍然是 reflect.Int。虽然 x 的静态类型是 MyInt。而不是 int。换句话说,Kind 无法从 MyInt 中区分 int,而 Type 能够。

2. 从反射对象到接口值的反射

如同物理中的反射,在 Go 中的反射也存在它自己的镜像。

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

// Interface 以 interface{} 返回 v 的值。
func (v Value) Interface() interface{}

能够这样作为结果

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

通过反射对象 v 能够打印 float64 的表达值。

然而,还能够做得更好。fmt.Println,fmt.Printf 等其它全部传递一个空接口值作为參数的函数。在 fmt 包内部解包的方式就像之前的样例这样。因此正确的打印 reflect.Value 的内容的方法就是将 Interface 方法的结果进行格式化打印(formatted print routine).

fmt.Println(v.Interface())

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

因为值是 float64,假设须要的话,甚至能够使用浮点格式化:

fmt.Printf("value is %7.1e\n", v.Interface())

输出: 3.4e+00 



再次强调。对于 v.Interface() 无需类型断言其为 float64。空接口值在内部有实际值的类型信息。而 Printf 会发现它。 



简单来说,Interface 方法是 ValueOf 函数的镜像,除了返回值总是静态类型 interface{}。



回想:反射能够从接口值到反射对象,也能够反过来。

3. 为了改动反射对象。其值必须可设置

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

假设执行这个代码,它报出神奇的 panic 消息

panic: reflect.Value.SetFloat using unaddressable value

问题不在于值 7.1 不能地址化;在于 v 不可设置。设置性是反射值的一个属性,并非全部的反射值有此特性。

Value的 CanSet 方法提供了值的设置性;在这个样例中,

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:" , v.CanSet())

打印

settability of v: false

对不可设置值调用 Set 方法会有错误。

可是什么是设置性? 



设置性有一点点像地址化,可是更严格。这是用于创建反射对象的时候。可以改动实际存储的属性。

设置性用于决定反射对象是否保存原始项目。当这样

var x float64 = 3.4
v := reflect.ValueOf(x)

就传递了一个 x 的副本到 reflect.ValueOf。所以接口值作为 reflect.ValueOf 參数创建了 x 的副本,而不是 x 本身。因此。假设语句

v.SetFloat(7.1)

同意运行。尽管 v 看起来是从 x 创建的。它也无法更新 x。

反之,假设在反射值内部同意更新 x 的副本。那么 x 本身不会收到影响。这会造成混淆,而且毫无意义。因此这是非法的,而设置性是用于解决问题的属性。

这非常奇妙?事实上不是。这实际上是一个常见的非同平常的情况。考虑传递 x 到函数:

f(x) 因为传递的是 x 的值的副本,而不是 x 本身,所以并不期望 f 能够改动 x。假设想要 f 直接改动 x,必须向函数传递 x 的地址(也就是。指向 x 的指针):

f(&x) 这是清晰且熟悉的,而反射通过相同的途径工作。假设希望通过反射来改动 x,必须向反射库提供一个希望改动的值的指针。

来试试吧。

首先像寻常那样初始化 x,然后创建指向它的反射值,叫做 p。

var x float64 = 3.4
p := reflect.ValueOf(&x) // 注意:获取 X 的地址。 fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:" , p.CanSet())

这样输出为

type of p: *float64
settability of p: false

反射对象 p 并非可设置的。并且我们也不希望设置 p,实际上是 *p。为了获得 p 指向的内容,调用值上的 Elem 方法,从指针间接指向,然后保存反射值的结果叫做 v:

v := p.Elem()
fmt.Println("settability of v:" , v.CanSet())

如今 v 是可设置的反射对象,如同演示样例的输出,

settability of v: true

而因为它来自 x,终于能够使用 v.SetFloat 来改动 x 的值:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

得到期望的输出

7.1
7.1

反射可能非常难理解,可是语言做了它应该做的,虽然底层的实现被反射的 Type 和 Value 隐藏了。务必记得反射值须要某些内容的地址来改动它指向的东西。

二结构体

在之前的样例中 v 本身不是指针,它仅仅是从一个指针中获取的。这样的情况更加常见的是当使用反射改动结构体的字段的时候。也就是当有结构体的地址的时候。能够改动它的字段。

这里有一个分析结构值 t 的简单样例。

因为希望对结构体进行改动,所以从它的地址创建了反射对象。设置了 typeOfT 为其类型。然后用直白的方法调用来遍历其字段(參考 reflect 包了解很多其它信息)。注意从结构类型中解析了字段名字,可是字段本身是原始的 reflect.Value 对象。

type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}

程序输出:

0: A int = 23
1: B string = skidoo

另一个关于设置性的要点:T 的字段名要大写(可导出),由于仅仅有可导出的字段是可设置的。

因为 s 包括可设置的反射对象,所以能够改动结构体的字段。

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

这里是结果:

t is now {77 Sunset Strip}

假设改动程序使得 s 创建于 t,而不是 &t,调用 SetInt 和 SetString 会失败,由于 t 的字段不可设置。

三 总结

反射的规则例如以下: 



从接口值到反射对象的反射 



从反射对象到接口值的反射 



为了改动反射对象,其值必须可设置 



一旦理解了 Go 中的反射的这些规则,就会变得easy使用了,尽管它仍然非常微妙。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

还有大量的关于反射的内容没有涉及到——channel 上的发送和接收、分配内存、使用 slice 和 map、调用方法和函数。

事例代码: https://github.com/ZhangzheBJUT/GoProject/blob/master/reflect/main.go

參考:  http://blog.golang.org/laws-of-reflection

Golang-interface(四 反射)的更多相关文章

  1. Golang 接口与反射知识要点

    目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...

  2. Golang通脉之反射

    什么是反射 官方关于反射定义: Reflection in computing is the ability of a program to examine its own structure, pa ...

  3. golang中的反射reflect详解

    先重复一遍反射三定律: 1.反射可以将"接口类型变量"转换为"反射类型对象". 2.反射可以将"反射类型对象"转换为"接口类型变量 ...

  4. golang实现四种排序(快速,冒泡,插入,选择)

    本文系转载 原文地址: http://www.limerence2017.com/2019/06/29/golang07/ 前面已经介绍golang基本的语法和容器了,这一篇文章用golang实现四种 ...

  5. Golang Interface 解析

    转自 https://zhuanlan.zhihu.com/p/27652856 先看一段代码: 123456789101112 func (x interface{}) { if x == nil ...

  6. Java Native Interface 四--JNI中引用类型

    本文是<The Java Native Interface Programmer's Guide and Specification>读书笔记 JNI支持将类实例和数组类型(如jobjec ...

  7. 【Go入门教程6】interface(interface类型、interface值、空interface{}、嵌入interface、反射)

    interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服. 什么是interface 简单 ...

  8. golang:interface{}类型测试

    在golang中空的interface即interface{}可以看作任意类型, 即C中的void *. 对interface{}进行类型测试有2种语法: 1. Comma-ok断言: value, ...

  9. golang:reflect反射

    因为之前一直以C++为主要开发语言,所以刚接触go语言中的reflect时感觉很懵逼,因此决定找资料彻底学习一下. 到底反射是什么? https://blog.golang.org/laws-of-r ...

随机推荐

  1. unity3d中获得物体的尺寸(size)

    1:获得诸如Plane.Cube的size.    1):可以为它们添加Collider,然后使用XXX.collider.bounds.size;该方法获得的size和缩放比例有关,是一一对应的,缩 ...

  2. SDOI2008仪仗队

    这题应该注意到与b2818的不同 一个点能被看见当且仅当它与(1,1)的横纵坐标的距离gcd为1 所以问题转化为x,y<=n-1,求gcd(x,y)=1的方案数 最后要加上2 代码: var i ...

  3. 嵌入式linux市场份额

    来自华清远见2014年度的调查统计数据显示,在嵌入式产品研发的软件开发平台的选择上,嵌入式Linux以55%的市场份额遥遥领先于其他嵌入式开发软件发平台,比去年增长了13个百分比,这已经是连续4年比例 ...

  4. [2015编程之美] 资格赛C

    #1150 : 基站选址 时间限制:2000ms 单点时限:1000ms 内存限制:256MB 描述 需要在一个N × M的网格中建立一个通讯基站,通讯基站仅必须建立在格点上. 网格中有A个用户,每个 ...

  5. Azure HDInsight HBase DR解决方案

    Sun wei  Sat, Feb 28 2015 3:07 AM Apache HBase是目前非常流行的NoSQL数据库,通过HDFS+Zookeep+Master+Region Server的架 ...

  6. Java多线程同步——生产者消费者问题

    这是马士兵老师的Java视频教程里的一个生产者消费者问题的模型 public class ProduceConsumer{ public static void main(String[] args) ...

  7. UVA 10600 ACM Contest and Blackout 次小生成树

    又是求次小生成树,就是求出最小生成树,然后枚举不在最小生成树上的每条边,求出包含着条边的最小生成树,然后取一个最小的 #include <iostream> #include <al ...

  8. 那些年一起踩过的坑 — Date类型序列化的问题

      坑在哪里?        序列化 和 反序列化 的时候对Date字段的格式设置不一致        例如:将Java bean序列化成Json string的时候 格式为 yyyy-MM-dd 解 ...

  9. LR参数化设置(转)

    LR学习笔记---参数设置 2010-10-20 14:58:55|  分类: 默认分类|举报|字号 订阅     LR在录制程序运行的过程中,VuGen(脚本生成器) 自动生成了包含录制过程中实际用 ...

  10. NOIP2011 计算系数

    1计算系数 给定一个多项式 (ax + by)k ,请求出多项式展开后 x n y m 项的系数. [输入] 输入文件名为 factor.in. 共一行,包含 5 个整数,分别为 a,b,k,n,m, ...