blog icon indicating copy to clipboard operation
blog copied to clipboard

Go reflection

Open imjoey opened this issue 8 years ago • 1 comments

前言

Go语言是静态编译型语言,要在Go中实现一些动态语言(如:Python)的特性就必须用到reflect包(反射)。本文首先介绍Go语言中的类型系统,然后讲解reflect包是如何工作的,最后通过分析github上的一段代码来加强理解。 本文的总结来自于实践,以及golang.org官网的博客文章。

类型(type)和接口(interface)

Go语言是静态语言,每个变量在编译时都会确定自己的静态类型,比如下面的i和j,静态类型分别是int和MyInt,尽管i和j有同样的底层类型(int),但二者之间不能直接赋值(必须通过类型转换)。

type MyInt int

var i int
var j MyInt

静态类型不同的变量不能直接赋值。

另外一个重要的类型是接口(interface),interface是一组函数的集合。在Go语言中,只要实现了interface内所有函数,该类型即作为interface的实现(而非Java或Python需要显式定义)。以io包中的io.Reader和io.Writer为例,两个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)
}

任何实现了Read()或Write()的类型都可以认作是实现了Reader或Writer接口,比如以下的os.Stdin、bufio.NewReader等类型,均可作为Reader的实现:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)

Go语言中,接口及其实现是靠是否实现接口内所有函数为准,不需显示定义。

空接口(interface{})是很特殊的类型,它是Go语言实现泛型的基础。

变量内部的(value, type)

Russ Cox在 这篇文章 中介绍,一个变量内部会有一对数据:(value, type),value表示 赋值给该变量的实际的值 ,type表示 实际值的类型 。

仍然是io.Reader为例,下面代码中,r的静态类型是io.Reader,在执行后,r内部的(value, type)是(tty, *io.File)。

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

正因为此时r内的type是*io.File(其内部实现了Write()函数),所以可以将r转换为io.Writer类型。但执行过后,w内承载的内容仍然是(tty, *io.File)。

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

甚至可以这样做,emtpy内承载的内容仍然是(tty, *io.File)。所以空接口(interface{})可以承载任何内容。

var empty interface{}
empty = w

Go语言的变量,内部的(value, type)是(真实值,真实值的类型),与定义的静态类型无关

将某个变量转换为interface{}类型,不需要type assertion的。empty = w,而不用r.(interface{})

reflect定律1 - reflect可以根据初始的接口类型的变量得到其反射对象

reflect的基本功能就是获得接口类型变量内部的(真实值, 真实值的类型)。Go语言在reflect包中提供了reflect.Value和reflect.Type两个类型,可分别通过reflect.ValueOf()和reflect.TypeOf()两个函数获取某个变量内部的(真实值, 真实值的类型)。

可通过调用reflect.Value的Type()方法得到对应的reflect.Type

以下代码介绍了reflect.TypeOf()函数的使用方法:

package main

import (
    "fmt"
    "reflect"
)

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

reflect.TypeOf()方法的定义是func TypeOf(i interface{}) Type,但实际上面代码向TypeOf函数传递的是变量x,并非是interface{};能这样做的原因就是,Go语言自动将x装包(包括了真实值和真实值的类型)到interface{}中,然后在TypeOf函数中先unpack然后处理。

以下代码是reflect.Value相关函数:

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))       // value: <float64 Value>
fmt.Println("type:", v.Type())                  // type: float64
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)  // kind is float64: true
fmt.Println("value:", v.Float())                // value: 3.4

.Kind()方法不同于reflect.TypeOf(),.Kind()返回的是 底层类型 ,比如对于var x MyInt,reflect.ValueOf(x).Kind()是reflect.Int,而reflect.TypeOf(x)则返回的是MyInt

reflect.Value的所有的”getter”和”setter”方法,均按最大范围的类型来转换,比如以下代码:

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,需要强制转成uint8后再赋值

reflect定律2 - reflect可以根据反射对象得到变量的真实值

因为reflect.Value可以通过调用reflect.Value的Interface()方法还原为interface{},Interface()函数定义如下:

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

这样的话,如果要打印reflect.Value对应的真实值,就可以使用fmt.Println(v.Interface())。fmt.Println()函数自动unpack参数,然后打印内部的真实值

reflect定律3 - 只有在变量可被寻址时,才能用reflection对象修改其真实值

reflect的一个很重要的用处就是在运行时修改变量(比如:struct)的值,修改的方法见以下代码:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // panic: reflect.Value.SetFloat using unaddressable value

可以看到,修改变量失败,原因是v不可寻址。那么可以用reflect.Value.CanSet()方法判断reflect.Value是否可修改,以下代码说明v是不可修改的。

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

那么怎么使用才能修改成功呢?这个可以参考传参时,形参 和 实参。以上代码中,传递给reflect.ValueOf()的参数x其实是 形参 ,既然是 形参 (也即是变量x的一个copy),那么修改它是无意义的,所以必须要像传 实参 一样,向reflect.ValueOf()函数传递 可被寻址 的参数。

要修改reflection相关对象,调用函数时必须传递用可寻址的变量,然后调用.Elem()方法得到真实的值,然后再修改 。详细如下:

var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("settability of p:", p.Elem().CanSet())  // settablitity of v: true
v := p.Elem()
v.SetFloat(7.1)
fmt.Println(v.Interface())  // 7.1
fmt.Println(x)              // 7.1

Structs的reflect

对于structs的反射,与上面介绍的类似,先看下面这段代码:

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 = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

打印:

0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}

在struct中,只有以大写字母开头的变量才是settable

结论

牢记Go语言reflect三大定律 :

  • reflection可以根据类型和值得到对应的反射对象

  • reflection可以根据反射对象得到类型和值

  • 只有在value是可修改时,才能用reflection对象修改值

下面看一段reflect包用法(含tag)的代码片段

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    FirstName string `tag_name:"tag 1"`
    LastName  string `tag_name:"tag 2"`
    Age       int    `tag_name:"tag 3"`
}

func (f *Foo) reflect() {
    val := reflect.ValueOf(f).Elem()

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %s,\t Field Value: %v,\t Tag Value: %s\n", typeField.Name, valueField.Interface(), tag.Get("tag_name"))
    }
}

func main() {
    f := &Foo{
        FirstName: "Drew",
        LastName:  "Olson",
        Age:       30,
    }

    f.reflect()
}

imjoey avatar Jan 20 '17 08:01 imjoey

根据最近的实践,越发觉得这句话很重要了:

Go语言的变量,内部的(value, type)是(真实值,真实值的类型),与定义的静态类型无关

imjoey avatar Jun 09 '17 03:06 imjoey