go的反射

原文 http://blog.golang.org/laws-of-reflection

翻译:

介绍:

    在计算机领域,反射是程序运行时类型判断,有很多好处,同时也很令人非常困惑,难以理解

    这篇文章将会尝试通过解释清楚go里面反射机制,每种语言的实现方式基本不同(有一些语言也不支持反射),但是该文章是讲Go,所以这篇文章的名字应该叫做“go的反射”

 因为反射基于 type 系统,所以我们从 go  的 types  说起

    Go 语言变量是静态类型,每个变量都有自己的类型,这是因为在编译的时候每个变量的类型都已经确定了,例如 int , float32, *MyType, []byte,等,如果我们定义

type MyInt int

var i int
var j MyInt

那么i 的类型是 int ,j的类型是 MyInt ,虽然他们的基础类型都是 int ,但是不能相互转换

interface 是一个重要的类型,它代表一系列方法的集合,一个interface值能够存储非interface类型的值(前提是这个值继承了个interface类型的方法),例如 io 包里面比较常用的io.Reader 和 io.Writer

// 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)
}

所有实现了Read(或者 Write) 方法的我们称之为实现了io.Reader(或者 io.Writer)接口,这意味着类型为io.Reader的变量可以接受所有拥有Read方法的类型的值

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

 但是要清楚的知道不管 r 的值是什么,r 的类型始终是io.Reader,并没有因为赋值的改变而改变

       空 interface 类型 是一个非常重要的interface类型

       它表示一个空的方法集合,并且能够接受所有类型的值,不管是0还是其他方法

       有人认为Go的动态类型的,但是这是误区,Go的变量是静态类型的。interface类型的值也一样是静态类型,即使在运行的时候interface变量存储了不同的值,他依然是interface类型

       我们应该清楚的了解这一点,因为反射和interfaces 紧密联系

接口的表示

       Russ Cox 曾经写过一篇关于Go语言里面interface值的博客,这里没有必要详细重复,但是会简单介绍下

      一个interface类型的变量可以存储具体的值,也可以存储类型描述符,更准确的说,interface类型能够存储实现了他的接口的所有的类型。例如

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

 r变量被赋值为tty( *os.File 类型).由于 *os.File 实现了Read 方法,即使 r 只有Read方法,但是r的值包含了所有的(*os.File)信息,这是为什么我们可以做下面的事情

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

这个表达式是成立的, r 也继承了 io.Writer ,所以我们能将他赋值给w。赋值之后,w会包含一对( tty, *os.File),和r里面包含的( tty, *os.File)一样。简单来说就是子类型能够赋值给父类型

然后我们能够这样做

var empty interface{}
empty = w

然后empty 变量能够包含同样的一对(tty*os.File)。这也说明空的interface类型能够存储所有的值并且含有值里面所有的信息

(这里我们不需要断言,因为显而易见w是空类型的子类,在这里例子里面,我们将一个Reader的值转换成Writer的值,我们需要断言,因为Witer并不是Reader的子类)

一个比较重要的细节是一个interface通常拥有一对(value, concrete type)(值, 明确的类型)并且不能拥有(value, interface type).interfaces 并没有 值

那么现在我们可以开始讲反射了

反射的第一章
1.从一个interface值反射成reflection对象

在一开始我们就已经解释了,反射只是在程序运行的时候检测变量的类型以及获取存储interface的值。我们需要了解reflect包里面的Type 和 Value,这两个类型让我们可以通过interface获取里面的值,reflect.TypeOf 和reflect.ValueOf这两个函数可以让我们从 reflect.Type 和 reflect.Value 获取到interface的值(同样,通过reflect.Value也很容易得到reflect.Type,但是现在我们先认为两个是独立的)

我们先从 TypeOf开始:

package main

import (
    "fmt"
    "reflect"
)

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

这会输出

type: float64

这是因为当我们调用 reflect.TypeOf(x) 是,x一开始存储的是空interface,然后reflect.TypeOf unpack了这个空interface,然后还原了这个类型的信息。

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,另一个是Tye和Value都有一个Kind方法返回他们存储了什么类型的值,例如 UintFloat64Slice, 等,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方法,但是我们使用它的时候应该先了解是否可以设置值,我们将会在第三章讨论

reflection 类库简化了许多,为了让API更加简单,例如getter和setter这一类的方法会用 int64 存储 integers类型,因此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对象的Kind()方法描述的是最基本的类型而不是静态声明的类型,如果一个reflection的对象包含了一个用户自定义的integer类型,那么:

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

即使x被声明的时候是MyInt类型,v的kind一样是reflect.Int

第二章
2.将reflection对象反射成interface值

     和物理上的反射类似,Go里面的反射也能反转

     我们可以通过interface的方法将reflect.Value还原成interface的值,也可以将打包好的type和value 的信息还原成原来的样子    

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

     因此我们可以这样打印出reflection 对象v的float的值

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

我们可以通过fmt.Printlnfmt.Printf等可以接受空interface 值的函数,输出想要值,这些函数会代替以上的代码还原出interface 的值。

第三章
3. 如果要修改reflection 对象,那么他的值必须是可以设置的

     第三章是最巧妙和疑惑的部分,但是如果我们理解了一开始的原理这将会变得很简单。

     下面是错误的代码,但是有利于学习

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

如果你运行这段代码,将会抛出panic(panic: reflect: reflect.Value.SetFloat using unaddressable value)

这个问题并不是由于7.1不是地址,这是由于v是不可以改变值,Settability 是reflection Value的一个属性, 但并不是所有的reflection Value都有这个属性

CanSet方法会告诉我们一个Value是否能够改变值,例如

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

输出

settability of v: false

    如果一个 non-settable (不可以改变值)的Value 调用了 Set方法,那么会发生错误,但是什么是Settability 呢?

    Settability  有点像地址,但是严格来说,这是reflection对象用来改变reflection对象的一个能力。Settability 是根据对象是否拥有旧元素来判断的

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

我们将x的拷贝传入了reflect.ValueOf,所以由传入的参数生成interface的值并不是x本身,只是一个拷贝,因此,以下语句如果成立

v.SetFloat(7.1)

那么,x并不会改变值,即使v好像是由x生成的,但是它只会改变x的拷贝的值,对原来x的值没有任何作用,这会变得很疑惑和没有一点用处,而settability 是为了避免这种状况

这可能有点不可思议,但是这是比较常见的情景,想一下,如果我们将x传到一个函数

f(x)

我们并不会希望f可以改变x的值,因为我们只是把x的拷贝传进去了,并不是x自身,如果我们希望f能够直接改变x,我们必须把x的地址传进去(即x的指针)

f(&x)

这是明确的也是常见的,reflection 也同样适用,如果我们想要通过reflection改变x的值,我们必须把指针作为参数

var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())

输出

type of p: *float64
settability of p: false

reflection 对象p并不是可以改变的。我们并不是想改变p,实际上是想改变*p。为了获取到p指向的东西,我们使用Value的Elem方法,这个方法会把结果(reflection Value类型)保存为v

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

现在v是可以设置的reflection对象,下面是输出的内容

settability of v: true

然后改变他会改变x,我们最后可以使用v.SetFloat来改变x的值

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

输出

7.1
7.1

反射比较难以理解,但是他和其他语言一样,只是需要通过reflection Types 和 Values 来实现。必须记住reflection Values必须通过地址来改变自己的值

转载于:https://my.oschina.net/prettyyjnic/blog/603890

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值