首页 文章资讯内容详情

GOLANG 反射法则

2026-06-01 2 花语

本文内容纲要:

译自[blog.golang.org/laws-of-reflection]

在计算机中,反射是程序通过类型,检测到它自己的结构能力;是一种元编程程;也是一个具大的混淆点

在本文中,我们将通过解释反射是如何在GO中工作的来澄清它。每个语言的反射模式是不同的。本文着重于GO,

所以后文中的反射都是指GO中的反射

1.类型和接口

因为反射是修建于类型系统之上,所以让我们从GO的类型开始讲吧。

GO是静态类型语言。每个变量都有一个静态类型。也就是说,每一个已经类型在编译时已经固定了其类型:int,float32...等

假如我们声明如下:

typeMyIntint

variint

varjMyInt

则i的类型为int;j的类型为MyInt;变量i和j有明显不同的静态类型,而潜在下层类型,他们可以彼此赋值不须要转换。

还有一种重要类型是接口类型,它代表了一组固定的方法集。一个接口变量能存储任一实体值,只要它实现了接口方法集。以大家所

熟知的接口为例,io.Reader和io.Writer,它们的类型摘自io包。

//Reader就是一个包含了基本读方法的接口

typeReaderinterface{

Read(p[]byte[(nint,errerror)

}

//Writher就是一个包含了基本写方法的接口

typeWriterinterface{

Write(p[]byte)(nint,errerror);

}

任何实现了上述读(写)方法签名的类型就说它实现了io.Reader(或者io.Writer).讨论这个的目标是,就是指一个io.Reader类型的变量

可以带有任何值,只要它的类型中有一个Read方法

varrio.Reader

r=os.Stdin

r=bufio.NewReader(r)

r=new(bytes.Buffer)

//等等....都可以的...

无论r带有什么实例值,r的类型总是io.Reader,明白这点是非常重要的;GO是静态类型,r的静态类型就是io.Reader。

另一个极其重要的接口例子就是空接口

interface{}

它代表一个空方法集。它可以被任何值满足。因为任何值都有0到多个方法

有些朋友说,GO的接口是动态类型的,这就是错误引导。他们是确实静态类型:一个接口类型的变量总是有一个相同的静态类型,

虽然,在运行时,一个存储在一个接口变量上的值可能会改变类型,但值总是满足接口。

我们需要准确的了解这些,因为反射和接口太相似了。

接口的表达

RussCox写了一篇博客关于GO语言接口值的表达【research.swtch.com/interface】,我就没有必须重复这个故事了。简单总结如下

接口类型的变量存成一个pair(对):值赋给变量和它的描述符descriptor;更准确的说,就是潜在下层的实例值实现了接口,它的类型

描述了下层实例值的完整类型。例如:

varrio.Reader

tty,err:=os.OpenFile("/dev/tty",os.O_RDWR,0)

ifer!=nil{

returnnil,err

}

r=tty

r包含了(值value,类型type)pair对,(tty,*os.File).注意,*os.File类型实现了远不只Read方法;虽然接口值提供只有Read方法。

内部值包含所有关于值的类型信息,这就是为什么我们可以如下处理

varwio.Writer

w=r.(io.Writer)//类型断言转换

这在个赋值表达式中是一个类型断言;它判言的是r里面的东西也实现了io.writer.并且我们能赋值给w,赋值后,w包含(tty,*os.File)对。

和之前r所包含的对是一样的的。接口的静态类型确定了通过接口变量那些方法可以被调用。虽然内部实例值可能有一个更大的方法集。

接着,我们也可以

varemptyinterface{}

empty=w

我们的空接口值empty,再一次包含了一样的(tty,*os.File)对。这是很方便的,空接口可以含有什么信息

一个重要的细节就是,内部pair对总是有一个(值value,实类型concretetype)并且没有(value,接口类型interfacetype)

现在我们可以谈反射了

反射法则

1、反射-从接口值到反射对象

从基本上讲,反射仅是一种用来检测存储在接口变量内部(值value,实例类型concrettype)pair对的一种机

制。开始,在反射包中,有两种类型我们需要了解类型和值。这两种类型让我们可以访问接口变量内容。

两个简单函数分别为reflect.TypeOf和reflect.Valueo,它们从接口变量中取出reflect.Type和

reflect.Value,(也可以从reflect.Value轻松获得reflect.Type,但是现在我们还是将Value和Type概念分

开来将)

让我们首先来讲TypeOf

packagemain

import(

"fmt"

"reflect"

)

funcmain(){

varxfloat64=3.4

fmt.Println("type:",reflect.TypeOf(x);

}

程序输出

type:float64

你可能会想这里的接口变量在哪里呀,因为程序看来像是传递的一个float64变量x,不是接口值,对于

reflect.TypeOf,包含了一空接口:

funcTypeOf(iinteface{})Type

当我们调用reflect.TypeOf(x),首先x被存在一个空接口中,它被当前该接口变量传入到reflect.TypeOf中;

reflect.TypeOf解包将空接口转为类型信息

reflect.ValueOf函数,找回值(从这里,我们省略代码模板,聚焦到执行代码上)

varxfloat64=3.4

fmt.Println("value:",reflect.ValueOf(x))

输出

value:

reflect.Type和reflect.Value都有方法配槽来让我们来检查与操作它们。一个重要的例子如,Value有一个

Type方法返回reflect.Value的类型Type。另一个就是Type和Value都有一个Kind方法返回一个常量表明它是

哪种存储类型Uint,Float64,Slice等等。此外,在Value上的有方法名如Int和Float让我们取出里面的值

varxfloat64=3.4

v:=reflect.ValueOf(x)

fmt.Println("type:",v.Type())

fmt.Println("kindisfloat64:",v.Kind()==reflect.Float64)

fmt.Println("value:",v.Float)

输出:

type:float64

kindisfloat64:true

value:3.4

还有方法如SetInt和SetFloat,但是使用它们要理解他们的设置能力,反射的第三条法则,讨论如下:

反射库有一些属性值得列出来,首先,保持API简单,值的getter和setter操作在它可以操作的最大的类型

上:对于所有有符号数为Int64;例如,值的Int方法返回一个int64,SetInt需要一个int64;它可能需要转换

成实际值

varxuint8=x

v:=reflect.ValueOf(x)

fmt.Println("type:",v.Type())//uint8

fmt.Println("kindisuint8:",v.Kind()==reflect.Uint8)//true

x=uint8(v.Uint())//v.Uint返回uint64

第二个属性就是,反射对象的Kind描述了潜在下层的类型Type,并不是静态类型。假如一个反射对象包含一个用户定义整形类型的值,如下:

typeMyIntint

varxMyInt=7

v:=reflect.ValueOf(x)

v的Kind仍然是reflect.Int,虽然x的静态类型是MyInt,而不是int,换名话说,Kind不区分int和MyInt,虽然Type能。

反射第二法则

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

就像物理反射一样,反射在GO生成自己的反像。

给定一个reflect.Value我们使用Interface方法能找回一个接口值;在effect方法打包了类型和值信息到一个接口中并返回结果。

//返回v的值作为一个接口变量

func(vValue)Interface()interface{}

由此我们可以说

y:=v.Inteface().(float64)//y有类型float64

fmt.Println(y)

。。。。。。。待续

本文内容总结:

原文链接:https://www.cnblogs.com/freebird92/p/4171292.html