Static Typed Go
Go作为一门静态类型的语言,所有变量都定义了其所属于的类型,不同类型的变量间不能随意赋值,例如:
1 2 3 4 5 6 7 |
|
a和b不是同一类型的变量,若尝试直接赋值将会报错cannot use b (type string) as type int in assignment
,不同类型变量间的赋值需要进行类型转换(Conversion),这点与C/C++里是一致的。在Go里,对于变量x与目标类型T,类型转换需要满足以下其中一种情况:
- x可以赋值为类型T的变量
- x与T有着一致的实现类型(underlying types)
- x与T都是匿名指针类型并且具有相同的实现类型
- x与T都是整数/浮点数类型
- x与T都是复数类型
- x是整数/bytes片段/runes,T是string
- x是string,T是bytes片段或runes
对于可以进行类型转换的变量,通过var c = float(100)
即可得到目标类型的变量。但在实际开发过程中,我们需要的不仅仅是基本类型的转换,譬如对于给定的接口类型:
1 2 3 4 5 |
|
只要实现了这三种方法,就可以认为该类型是合法的、可操作的,但由于无法确定最终传入变量的类型,我们需要在使用前将其转化为我们已知的类型。这种情况下由于类型转化(Conversion)只关心数据,我们需要类型断言(Type Assertion)来帮助我们:
1 2 3 4 5 |
|
类型断言判断变量是否为nil
以及是否实现了断言所需要的接口,若断言通过将得到一个目标类型的变量,否则将出现run-time panic
。若不希望在断言失败时程序可以继续执行,可以通过z, ok := y.(I)
的形式判断变量断言是否成功,若失败z
为目标类型的零值。
类型断言应用的常见例子是对JSON数据的解析。Go标准库中对JSON类与数组的解析返回的结果分别是map[string]interface{}
与[]interface{}
,若想正确访问数据我们就需要对返回结果进行类型断言:
1 2 3 4 5 6 7 8 9 10 11 |
|
Reflection
通过类型转换和类型断言,我们基本能够解决大多数问题,但单是可以解决是不够的,我们还需要更加方便(优雅)的解决方案。考虑这样一个场景,我们定义一个配置结构用来保存服务的配置信息,结构中用了多种类型:
1 2 3 4 5 |
|
同时我们将服务的配置保存在文本中,在服务加载时读取进来:
Port=8000
Path=/tmp/go_reflection
Debug=true
对配置文件解析我们基本可以得到(string, []byte)这样的元组,我们需要将数据存储到Config
中,由于结构中已经定义了对应的类型,因此我们在存储时需要进行类型转换:
1 2 3 4 5 6 7 8 9 10 11 |
|
对于int64
类型我们需要strconv.ParseInt
,对于string
我们需要string()
,对于bool
我们需要strconv.ParseBool
,在上面这个例子中我们可以通过switch
来判断每个配置项需要进行的转换,但在实际的应用中配置可能多达数十项,继续使用这种原始的方法简直就是一个噩梦。这时我们需要通过Go的Reflect模块来帮助我们更优雅地解决这个问题。
反射(Reflection)作为元编程的一种形式,赋予了我们在运行时判断变量类型的能力。Go的reflect
模块通过将数据封装在reflect.Type
与reflect.Value
中,提供了一系列方法让我们“动态”地去判断变量的类型:
1 2 3 4 5 6 7 8 |
|
在我们配置文件的例子中,我们可以根据每个配置的Key动态判断该进行何种类型转换并存储到那个属性中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
这样,我们只需要通过config.Set("Port", []byte("8000"))
一个方法就可以正确地存储配置信息,就算结构定义有变更也不需要重写解析方法。关于反射Golang Blog上有一篇详细的文章The Laws of Reflection,可以更好地帮助我们Go中反射的细节与需要注意的地方,值得一读。
有疑问加站长微信联系(非本文作者)