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.Type
下toType
是一个改变数据类型的方法:
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官方网站的使用场景:
- 从interface到reflect对象
- 从reflect对象到interface
- 改变一个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类型作为参数,因此在我们运行函数内部时需要知道参数的值以及类型的时候,reflect
的get
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
也让我们拥有了一部分动态语言的特性,你可以很容易获取参数的类型以及值,在使用它的时候。
有疑问加站长微信联系(非本文作者)