谈谈Go语言的反射三定律

日期: 2019-12-14 02:09 浏览次数 :

简介

率先次知道反射的时候依旧广新春前在这个学院里玩 C# 的时候。这时总是弄不通晓这一个纷纷的实物能有哪些实际用项……然后发掘 Java 有其朝气蓬勃,后来发觉 PHP 也是有了,再后来 Objective-C、Python 什么的也都有……以至连 Delphi 也许有 TTiguanttiContext……反射无处不在!!!

Reflection(反射)在Computer中象征 程序能够检查本身结构的力量,尤其是连串。它是元编程的大器晚成种样式,也是最轻便令人迷惑的一片段。

Go 作为叁个云集的今世系统级语言,当然也亟需有,必需的!

就算如此Go语言未有持续的定义,但为了便利驾驭,要是四个struct A 完毕了 interface B的有着办法时,我们称为“世袭”。

大咖 罗布 Pike 的那篇随笔相对圆满的介绍了 Go 语言中的反射的机制已经使用。感觉值得研读,于是翻译于此。

系列和接口

———-翻译分界线———-

反射组建在等级次序系统之上,由此大家从品种底工知识谈到。

反射的规行矩步

在运作时反射是先后检查其所具备的协会,尤其是种类的豆蔻梢头种力量;这是元编制程序的生机勃勃种格局。它同有时间也是招致混淆的重大根源。

在这里篇小说中校试图显明解释在 Go 中的反射是如何工作的。各类语言的反射模型都不及(相同的时候广大语言根本不援救反射)。可是那篇小说是有关 Go 的,由此接下去的内容“反射”那意气风发词表示“在 Go 中的反射”。

Go是静态类型语言。每种变量都有且独有一个静态类型,在编写翻译时就曾经规定。比如 int、float32、*MyType、[]byte。 如果大家做出如下宣示:

连串和接口

鉴于反射营造于类型系统之上,就从复习一下 Go 中的类型起头吧。

Go 是静态类型的。每二个变量有一个静态的类型,也正是说,有多少个已知类型并且在编写翻译时就规定下来了:int,float32,*MyType,[]byte 等等。假若定义

type MyInt int
var i int
var j MyInt

那么 i 的连串为 int 而 j 的品类为 MyInt。纵然变量 i 和 j 有近似的后面部分类型,它们仍是有两样的静态类型的。未经转变是无法相互赋值的。

在品种中有三个至关心器重要的种类正是接口类型,表明了定位的叁个主意集合。一个接口变量可以储存跋扈实际值(非接口),只要那个值直线了接口的法门。无人不晓的三个例子就是is io.Reader 和 io.Writer,来自 io 包的类型 Reader 和 Writer:

// Reader 是包裹了基础的 Read 方法的接口。.
type Reader interface {
    Read(p []byte) (n int, err os.Error)
}

// Writer 是包裹了基础 Write 方法的接口。
type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

别的用那一个宣称完成了 Read(或 Write)方法的品类,能够说它完结了 io.Reader(或 io.Writer)。基于本探究来讲,那意味 io.Reader 类型的变量能够保留大肆值,只要那些值的项目完结了 Read 方法:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// 等等

有三个专门的学问是必然要鲜明的,无论 r 保存了什么值,r 的种类总是 io.Reader:Go 是静态类型,而 r 的静态类型是 io.里德r。

接口类型的叁个极端重要的例证是空中接力口:

interface{}

它表示空的章程群集,由于其他值都有另个恐怕八个法子,所以任何值都得以满足它。

也会有些人讲 Go 的接口是动态类型的,可是那是生龙活虎种误解。它们是静态类型的:接口类型的变量总是有着类似的静态类型,这几个值总是知足空切口,只是存款和储蓄在接口变量中的值运转时也可能有望被改换类型。

对此有着这么些都必须严谨的相比较,因为反射和接口紧凑相关。

type MyInt int

var i int
var j MyInt

接口的特点

Russ Cox 已经写了黄金时代篇详细介绍 Go 中接口值的风味的博文谈谈Go语言的反射三定律。。所以不用在那处再次整个传说了,不过大约的总计依然不能够贫乏的。

接口类型的变量存款和储蓄了五个内容:赋值给变量实际的值和那个值的门类描述。更精确的说,值是底层实现了接口的莫过于数目项目,而项目描述了那个体系全体的花色。举个例子上边,

var r io.Reader
tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil { return nil, err }
r = tty

用格局的花样来发挥 r 包括了的是 (value, type卡塔尔(قطر‎ 对,如 (tty, *os.File卡塔尔。注意类型 *os.File 除了 Read 方法还完结了其余方法:固然接口值仅仅提供了拜见 Read 方法的或然,不过中间含有了那个值的完好的类型音信。那也便是为什么能够这样做:

var w io.Writer
w = r.(io.Writer)

在此个赋值中的断言是多个品种断言:它预见了 r 内部的条文相同的时候也促成了 io.Writer,由此得以赋值它到 w。在赋值之后,w 将会含有 (tty, *os.File卡塔尔(قطر‎。跟在 r 中保留的毫发不爽。接口的静态类型决定了哪位方法可以透过接口变量调用,尽管中间实际的值恐怕有三个更加大的点子集。

接下去,能够那样做:

var empty interface{}
empty = w

而空中接力口值 e 也将含有相似的 (tty, *os.File卡塔尔。那很便利:空切口能够保留任何值同不经常候保留关于丰富值的装有音讯。

(这里不需重要项目目断言,因为 w 是早晚满足空切口的。在这里个例子中,将八个值从 Reader 变为 Writer,由于 Writer 的艺术不是 Reader 的子集,所以就必得旗帜分明使用途目断言。)

三个很主要的内幕是接口内部的对连年 (value, 实际类型卡塔尔(قطر‎ 的格式,而不会有 (value, 接口类型卡塔尔国 的格式。接口不能够保留接口值。

近些日子筹划好来反光了。

上面包车型大巴代码中,变量 i 的品种是 int,j 的品种是 MyInt。 所以,纵然变量 i 和 j 具备协作的底层类型 int,但它们的静态类型并不平等。不通过类型调换直接相互作用赋值时,编写翻译器会报错。

反射的第一条法规

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

在着力的层面上,反射只是贰个反省存款和储蓄在接口变量中的类型和值的算法。从头来说,在 reflect 包中有三个类型须求精晓:Type 和 Value。那五个种类使得可以访谈接口变量的源委,还应该有七个轻便的函数,reflect.TypeOf 和 reflect.ValueOf,从接口值中分头获得 reflect.Type 和 reflect.Value。(相同,从 reflect.Value 也十分轻巧能够拿到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 能够。

有关项目,二个最首要的归类是 接口类型(interface),每一种接口类型都代表一定的章程集结。二个接口变量就足以储存(或“指向”,接口变量相通于指针)任何项指标具体值,只要那么些值达成了该接口类型的具有办法。意气风发组广为人知的事例是 io.Readerio.Writer, Reader 和 Writer 类型来源于 io包,表明如下:

反射的第二条法规

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

犹如物理中的反射,在 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卡塔尔(英语:State of Qatar)?因为 v 是三个reflect.Value;这里希望是它保存的实际的值。)由于值是 float64,假设要求的话,以致足以利用浮点格式化:

fmt.Printf("value is %7.1en", v.Interface())

下一场就得到那个

3.4e+00

重复重申,对于 v.Interface(卡塔尔 没有需求项目断言其为 float64;空切口值在当中有实际值的类型信息,而 Printf 会开掘它。

简轻易单的话,Interface 方法是 ValueOf 函数的镜像,除了重临值总是静态类型 interface{}。

追思:反射能够从接口值到反射对象,也得以反过来。

// Reader is the interface that wraps the basic Read method.
type Reader interface {
 Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
 Write(p []byte) (n int, err error)
}

反射的第三条准则

  1. 为了改进反射对象,其值必得可安装。

其三条军规是并世无两精细和吸引的,但是假如从第4个准则开头,依旧得以令人精晓的。

此处有部分不可能干活的代码,值得学习。

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 不可设置。设置性是反射值的壹天性质,实际不是具有的反射值有它。

值的 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 隐蔽了。必须记得反射值必要一些内容的地址来改过它指向的东西。

其他完结了 Read(Write)方法的连串,我们都叫作世襲了 io.Reader(io.Writer)接口。换句话说, 三个项目为 io.Reader的变量 能够针对(接口变量形似于指针)任何项指标变量,只要那一个项目实现了Read 方法:

结构体

在事前的例子中 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 = %vn", 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 的字段不可设置。

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

总结

双重提示,反射的平整如下:

  • 从接口值到反射对象的反光。
  • 从反射对象到接口值的反光。
  • 为了矫正面与反面射对象,其值必得可安装。

设若精晓了 Go 中的反射的那么些准则,就能够变得轻巧采纳了,就算它依然很好看妙。那是叁个有力的工具,除非真得有必不可缺,不然应当防止选拔或小心使用。

再有大量的有关反射的剧情未有涉嫌到——channel 上的出殡和吸纳、分配内部存款和储蓄器、使用 slice 和 map、调用方法和函数——不过那篇作品已经够长了。这个话题将会在后来的稿子中相继解说。

Rob Pike 撰写,2011年9月

要每十七日记住:不管变量 r 指向的具体值是何等,它的类型永久是 io.Reader。再重新贰遍:Go语言是静态类型语言,变量 r 的静态类型是 io.Reader

二个这几个充裕重大的接口类型是空中接力口,即:

interface{}

它代表一个空集,未有别的措施。由于别的现实的值都有零或更五个格局,由此类型为interface{} 的变量能够存款和储蓄任何值。

有些人说,Go的接口是动态类型的。这些说法是错的!接口变量也是静态类型的,它世代唯有一个平等的静态类型。倘诺在运转时它存款和储蓄的值产生了转移,那几个值也不得不满意接口类型的法子集结。

是因为反射和接口两个的涉嫌很留神,我们必需弄清那点。

接口变量的代表

Russ Cox 在二〇〇八年写了豆蔻梢头篇小说介绍 Go中接口变量的象征方式,这里大家没有必要重新全体的细节,只做三个总结的总括。

Interface变量存储一对值:赋给该变量的现实的值、值类型的陈述符。更规范一点以来,值正是落到实处该接口的平底数据,类型是底层数据类型的陈说。例如:

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
 return nil, err
}
r = tty

在这里个例子中,变量 r 在布局上包括叁个 (value, type卡塔尔 对:(tty, os.File卡塔尔国。注意:类别 os.File 不唯有达成了 Read 方法。即使接口变量只提供 Read 函数的调用权,不过底层的值包涵了关于这一个值的具备类型消息。所以大家可以做这么的类型转变:

var w io.Writer
w = r.(io.Writer)

上边代码的第二行是多少个门类断言,它确定变量 r 内部的实际值也三番五遍了 io.Writer接口,所以工夫被赋值给 w。赋值之后,w 就本着了 (tty, *os.File卡塔尔国对,和变量 r 指向的是同一个 (value, type卡塔尔(قطر‎对。不管底层具体值的办法集有多大,由于接口的静态类型约束,接口变量只好调用特定的生机勃勃对方法。

我们一而再往下看:

var empty interface{}
empty = w

那边的空中接力口变量 empty 也包蕴 (tty, *os.File卡塔尔(قطر‎对。这点相当的轻便通晓:空中接力口变量可以积存任何具体值以致该值的具有描述新闻。

精心的意中人或许会意识,这里未有选取项目断言,因为变量 w 满足空切口的具备办法(轶事中的“无招胜有招”)。在前二个例子中,大家把一个切实值 从 io.Reader转换为io.Writer时,供给显式的档次断言,是因为io.Writer的章程会集不是io.Reader 的子集。

其余索要小心的一些是,(value, type卡塔尔 对中的 type 必得是 具体的档案的次序(struct或基本类型),不可能是 接口类型。 接口类型不可能积存接口变量。

至于接口,我们就介绍到这里,上面大家看看Go语言的反光三定律。

反射第一定律:反射能够将“接口类型变量”调换为“反射类型对象”。

注:这里反射类型指 reflect.Typereflect.Value

从用法上来说,反射提供了大器晚成种体制,允许程序在运作时检查接口变量内部存款和储蓄的 (value, type卡塔尔(英语:State of Qatar) 对。在最早步,大家先明白下 reflect 包的二种档期的顺序:Type 和 Value。这两系列型使访谈接口内的多寡变成可能。它们对应两个简易的章程,分别是 reflect.TypeOfreflect.ValueOf,分别用来读取接口变量的 reflect.Typereflect.Value部分。当然,从 reflect.Value也超级轻易获得到 reflect.Type。近年来我们先将它们分别。

率先,大家下看 reflect.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,并未传递接口。事实上,接口就在此边。查阅一下TypeOf 的文书档案,你会开采 reflect.TypeOf 的函数签字里包括三个空切口:

// TypeOf returns the reflection Type of the value in the 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.Typereflect.Value 都有不少办法,大家能够检查和使用它们。这里大家举多少个例证。类型 reflect.Value 有叁个方法 Type(卡塔尔,它会重回三个 reflect.Type品种的指标。Type和 Value都有二个名称叫 Kind 的方法,它会回来多个常量,表示底层数据的类型,习感到常值有:Uint、Float64、Slice等。Value类型也可以有风流倜傥对看似于Int、Float的措施,用来领取底层的数据。Int方法用来提取 int64, Float方法用来领取 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,在座谈它们从前,我们要先明了“可修改性”(settability),这一表征会在“反射第三定律”中进行详尽表达。

反射库提供了数不尽值得列出来单独研商的质量。首先是介绍下Value 的 getter 和 setter 方法。为了保障API 的简要,这七个主意操作的是某意气风发组项目范围最大的相当。比如,管理任何含符号整型数,都使用 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 returns a uint64.

其次个属性是反光类型变量(reflection object)的 Kind 方法 会再次回到底层数据的花色,实际不是静态类型。假使三个反光类型对象包涵贰个顾客定义的整型数,看代码:

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

上边包车型大巴代码中,纵然变量 v 的静态类型是MyInt,不是 int,Kind 方法照旧重临reflect.Int。换句话说, Kind 方法不会像 Type 方法豆蔻年华致区分 MyInt 和 int。

反射第二定律:反射能够将“反射类型对象”调换为“接口类型变量”。

和物军事学中的反射近似,Go语言中的反射也能创设谐和反面类型的靶子。

依靠一个 reflect.Value 类型的变量,我们能够利用 Interface 方法复苏其接口类型的值。事实上,这些点子会把 type 和 value 信息打包并填充到叁个接口变量中,然后重临。其函数注解如下:

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

接下来,我们能够通过断言,复苏底层的具体值:

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

下边这段代码会打字与印刷出一个 float64 类型的值,也正是 反射类型变量 v 所代表的值。

其实,大家能够更加好地应用那后生可畏特点。标准库中的 fmt.Printlnfmt.Printf等函数都选拔空切口变量作为参数,fmt 包里面会对接口变量举行拆包(前边的例子中,大家也做过相符的操作)。因而,fmt 包的打字与印刷函数在打字与印刷 reflect.Value 类型变量的数额时,只供给把 Interface 方法的结果传给 格式化打字与印刷程序:

fmt.Println(v.Interface())

您大概会问:问哪些不直接打字与印刷 v ,比如 fmt.Println(v卡塔尔国? 答案是 v 的等级次序是 reflect.Value,大家必要的是它存款和储蓄的具体值。由于底层的值是四个float64,大家得以格式化打字与印刷: