golang中的反射

wowzai · · 4857 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Golang中的反射


今天看了一下golang中的反射,觉得golang中的反射还是比较容易使用的。这边文章基本是自己对于这篇的翻译。

类型和接口

由于反射是基于类型系统(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:",reflectValueOf(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)  

bingo!如果你实验了以上代码,你就会发现在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.


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


---如有错误,欢迎指正---


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:wowzai

查看原文:golang中的反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4857 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传