[Go] 反射 - reflect.ValueOf()

类型 和 接口

由于反射是基于类型系统(type system)的,所以先简单了解一下类型系统。

首先 Golang 是一种静态类型的语言,在编译时每一个变量都有一个类型对应,例如:int, floate32, []byte, *MyType 等等。如果我们这样声明:

type MyInt int

var i int
var j MyInt

上面的 i 是 int 类型的, j 是 MyInt 类型的。i 和 j 是不同的静态类型,尽管他们都有相同的相关类型(这里就是 int),他们不能互相赋值除非通过强制转换。

一种非常重要的类型分类是接口类型,接口代表中方法的集合。只要一个值实现了接口定义的方法,那么这个值就可以存储这个具体的值。一个著名的例子就是 io 包中的 Reader 和 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{}

这个代表一个空的方法集合并且满足任何值,只要这个值有零个或者多个方法。

有人说 Golang 中的 interface 是动态类型的,这个一个误导。一个 interface 类型的变量拥有相同的静态类型,尽管运行时这个变量的值会发生改变,但是都是满足一直都是满足这个 interface 的。

##interface的表示

Russ Cox 曾经写个 一篇博文 详细讨论了 Golang 中的 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 包含了一个 (value, type) 对,具体的就是(tty, *os.File)。*os.File 实现了 Read 等很多方法,但是 io.Reader 的接口之允许访问 Read 方法,所以我们还可以这样做:

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

通过类型断言 (type assertion),因为 r 照样实现了 io.Writer,所以我们可以将 r 赋值给 w。

Relection goes from interface value to reflection object

本质上来说,反射就是一种检查接口变量的类型和值的机制。最基本的我们要知道 reflect.Type 和 reflect.Value。可以通过 reflect.TypeOf 和 reflect.ValueOf 来得到接口变量的 Type 和 Value,同样可以通过 reflect.Value 轻松得到 reflect.Type。

package main

import (
  "fmt"
  "reflect"
)

func main() {
  var x float = 3.14
  fmt.Println("type:",reflect.TpyeOf(x))
}

结果是:

type: float64

同样可以通过 reflect.ValueOf 轻松得到 Value:

var x float64 = 3.14
fmt.Println("vlaue:", reflect.ValueOf(x))

结果是:

value: <float64 Value>

reflect.Value 和reflect.Type 有很多方法可以供我们使用,具体可以查看 API,例如:

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

结果:

type: float64
kind is float64: true
value: 3.14

1、大的数据类型可以包含小的数据类型,例如 int64 可以是任意的整数 (int8, uint8, int32 等),但是需要转换一下。

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:",v.Type())
fmt.Println("kind is uint8:", v.Kind() == reflect.Uint8)
x = uint8(v.Uint())

2、如果 Kind() 方法是描述相关的类型,而不是静态的类型,例如用户自定义了一个类型:

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

那么 v.Kind() 的返回值是 reflect.Int,尽管 x 的静态类型是 MyInt 而不是 int。Kind() 无法描述一个 MyInt 的 int 但是 Type 可以。

Relection goe s from reflection object to interface value

与上面相反,从 reflect object 到 interface value 则是非常容易的。只要通过 Value 的 Interface 方面法就可以获得 interface value 了。

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

因为 fmt.Println()、fmt.Printf() 接受空接口的值 (empty interface) 作为参数,我们可以这样:

fmt.Println(v.Interface())
fmt.Printf("value is %7.1e\n", v.Interface())  // print: 3.1e+00

To modify a reflection object,the value must be settable

上面我们知道了 interface value 和 reflection object 之间的反射,那么我们如何改变一个 reflection object 呢?

我们是否可以通过 Value 的 SetXXX() 方法来实现呢,就像下面一样:

var x float64 = 3.14
v := reflect.ValueOf(x)
v.SetFloat(2.8)  

如果你实验了以上代码,你就会发现在 SetFloat() 的时候会 panic,那么为什么呢?

原因和简单,就是因为 v 不可以被 Set,如何知道一个 Value 是否可以被 Set 呢?通过 CanSet() 方法就可以了。

var x float64 = 3.14
v := reflect.ValueOf(x)
fmt.Println("settability of v:",v.CanSet())  // prints: settability of v: false

可以不可以被 Set 是通过 reflection object 是否持有原始的变量值,例如这样:

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

这段代码中传给 reflect.ValueOf 的是 x 的一个副本,而不是 x 本身,那么 v 就是不可以被 Set 的,所以通过 SetFloat() 方法是不被允许的。那么如何才能被允许被 Set 呢,也许有人会想到了对于函数,我们可以通过传给函数一个指向参数的指针来达到修改参数本身的作用,同样的道理,这里也可以通过传指针:

var x float64 = 3.14
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

我们可以看到 p 的类型是 *float64,而不是 float64 了,但是为什么还是不可以被 Set 呢,因为这里 p 是一个指针,我们并不是要 Set 这个指针的值,而是要 Set 指针所指内容的值(也就是 *p),所以这里 p 仍然是不可被 Set 的,我们可以通过 Value 的 Elem() 方法来指针所指向内容的 Value:

v := p.Elem()
fmt.Println("settability of v:",v.CanSet())  // prints: settabiliyty of v : true

这个时候我们就可以调用 Value 的 Set 方法:

v.SetFloat(2.8)
fmt.Println(v.Interface())  // prints: 2.8
fmt.Println(x)              // prints: 2.8

好了,那么对于一个 Struct 如何来反射呢?我相信你看了这个例子应该就会如何使用了:

type T struct {
  A int
  B string
}

t := T{23,"hello world"}
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 = hello world

同样可以通过以下类似的代码来修改 T 的值:

s.Field(0).SetInt(22)
s.Field(1).SetString("XXOO")

好了,反射就基本如此吧,记住三点即可: 

1. Reflection goes from interface value to reflection Object.

2. Reflection goes from refelction object to interface value.

3. To modify a reflection object, the value must be settable.

 

 

延伸阅读:

Go - 反射中 函数 和 方法 的调用 - v.Call() 

 

 

参考:

http://golang.org/doc/articles/laws_of_reflection.html

http://blog.csdn.net/wowzai/article/details/9305147

Golang 反射法则

posted @ 2017-01-21 16:47  52php  阅读(7281)  评论(0编辑  收藏  举报