阅读 899

[译]理解Go的reflect

原文:medium.com/better-prog…

Go是一个强类型的静态编程语言。然而,一些Go的特性让它看起来又像是一门动态语言。例如,如果你不确定你接收的参数的类型,你可以使用interface来接收所有类型的参数传递。

记住只有interface是有reflect属性的

我们注意到interface允许Go实现多态。没有任何一种类型是特别需要强调的。可以是string int64 float32 甚至是集合(array/map)。但计算机运行这些代码时候,reflect帮助检查,修改其自身的结构与行为。这个过程允许我们知道对象的类型以及内存在运行时的结构。

我们为什么需要reflect?

  • 允许提前定义参数类型(通常发生在暴露的API上)
  • 函数能根据传参动态执行

reflect的缺点

  • 影响代码可读性
  • 屏蔽了代码编译时的错误检查。作为动态语言,Go的编译器可以提前检测数据类型的错误,在编译的时候。当数据在interface中没有特性指明类型的时候,服务器会有在运行代码时候出现panic的风险
  • 降低了整体的性能。使用reflect需要服务端去做额外的工作去获取参数的值以及具体的类型,因此,尽量避免在一些很重要的参数上使用interface

reflect的两个基础功能

reflect两个主要功能是reflect.Type以及reflect.Value

简单的说reflect.Type提供参数的实际类型,当reflect.Value结合_type data一起使用的时候可以允许开发者读取或改写参数的值。

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
复制代码

然后你可以使用fmt.Printf()%T来将参数进行格式化来或者reflect.TypeOf的结果,如下:

fmt.Printf("%T", 3) //int
复制代码

reflect.TypetoType是一个改变数据类型的方法:

func toType(t * rtype) Type {
    if t == nil {
    return nil
}
 
return t
}
复制代码

换句话说,reflect.Value返回一个储存在interface{}中的变量。已经有很多方法包含如SetLen(n int)SetMapIndex(key, val Value),Int(),TrySend(x refelect.Value)等等。在完整版的文档上,可以参考src/reflect/value.go

三个使用reflect的场景

来自Go官方网站的使用场景:

  1. 从interface到reflect对象
  2. 从reflect对象到interface
  3. 改变一个interface,但它的值必须是可被改变的

经典的例子如下:

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

服务器在运行这段代码时候会panic,因为v并不是x,而是x的靠背。因此,所有对于v的修改都是禁止的。

所以,我们需要使用指针来解决这个问题

var x float64 = 3.4
y := reflect.ValueOf(&x)

fmt.Println(“type of y”, y.Type()) // *float64
fmt.Println(“settability of y:”, y.CanSet()) // false
复制代码

这时候y仍然不能代替x,你可以通过y.Elem()来修改

z := y.Elem()
z.SetFloat(7.1)

fmt.Println(z.Interface()) // 7.1
fmt.Println(x) // 7.1
复制代码

可以注意到指针会对所指向的值一并作出修改,也就是x

reflect的应用

reflect被广泛应用到对象序列化,fmt相关函数,以及ORM等等上。

JSON序列化

在Go中有两个序列化与反序列化的函数

func Marshal(v interface{})([]byte, error)
func Unmarshal(data []byte, v interface{}) error
复制代码

两个函数都接收interface类型作为参数,因此在我们运行函数内部时需要知道参数的值以及类型的时候,reflectget set方法就能起到作用了

DeepEqual函数

在测试一个功能的时候,我们往往需要知道两个变量是否完全一致。例如,判断一个slice中所有的元素是否相同或者检查两个map中所有的key对应的value是否相同。这时就需要DeepEqual函数

func DeepEqual(x, y interface{}) bool
复制代码

DeepEqual接收两个interface的参数。你可以传入任意值,它会返回一个布尔值表示传入的两个参数是否完全相等。

等一下,什么叫做 deeply 相等,看看下面例子

type FirstInt int
type SecondInt int

func main() {
    m := FirstInt(1)
    n := SecondInt(1)
    fmt.Println(reflect.DeepEqual(m, n)) // false
}
复制代码

在上面例子中虽然m,n都是1,但是他们的数据类型是不一样的,一个是FirstIn类型,一个是SecondInt类型。所以它们是不相等的。

总结

Go作为一门静态语言,我们可以非常明确在语言编写的弹性上来说相比于例如Python这样的动态语言来说肯定是有局限性的。但是通过使用reflect也让我们拥有了一部分动态语言的特性,你可以很容易获取参数的类型以及值,在使用它的时候。

关注下面的标签,发现更多相似文章
评论