欢迎来到爱学习爱分享,在这里,你会找到许多有趣的技术 : )

Reflection in Golang【Go语言中文网】

GO语言 lbbniu 3981℃

点击下方 查看原文,更好的排版

Static Typed Go

Go作为一门静态类型的语言,所有变量都定义了其所属于的类型,不同类型的变量间不能随意赋值,例如:

var a int

var b string

a = 1

b = "codeb2cc"

a = b

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)即可得到目标类型的变量。但在实际开发过程中,我们需要的不仅仅是基本类型的转换,譬如对于给定的接口类型:

Type I interface {

Read(b Buffer) bool

Write(b Buffer) bool

Close()

}

只要实现了这三种方法,就可以认为该类型是合法的、可操作的,但由于无法确定最终传入变量的类型,我们需要在使用前将其转化为我们已知的类型。这种情况下由于类型转化(Conversion)只关心数据,我们需要类型断言(Type Assertion)来帮助我们:

var x I

var y File

x = y.(I)

z, ok := y.(I)

类型断言判断变量是否为nil以及是否实现了断言所需要的接口,若断言通过将得到一个目标类型的变量,否则将出现run-time panic。若不希望在断言失败时程序可以继续执行,可以通过z, ok := y.(I)的形式判断变量断言是否成功,若失败z为目标类型的零值。

类型断言应用的常见例子是对JSON数据的解析。Go标准库中对JSON类与数组的解析返回的结果分别是map[string]interface{}与[]interface{},若想正确访问数据我们就需要对返回结果进行类型断言:

b := []byte(`{"Foo":"Bar", "Hello": ["W", "o", "r", "l", "d"]}`)

var f interface{}

err := json.Unmarshal(b, &f)

// f类型为interface{}, 需要转换为map[string]interface{}来访问数据

m := f.(map[string]interface{})

fmt.Println(m["Foo"])

// 同样的, m["Hello"]的类型为interface{}, 在正常访问前我们需要转换为对应的类型

n := m["Hello"].([]byte)

Reflection

通过类型转换和类型断言,我们基本能够解决大多数问题,但单是可以解决是不够的,我们还需要更加方便(优雅)的解决方案。考虑这样一个场景,我们定义一个配置结构用来保存服务的配置信息,结构中用了多种类型:

type Config struct {

Port int64

Path string

Debug bool

}

同时我们将服务的配置保存在文本中,在服务加载时读取进来:

Port=8000

Path=/tmp/go_reflection

Debug=true

对配置文件解析我们基本可以得到(string, []byte)这样的元组,我们需要将数据存储到Config中,由于结构中已经定义了对应的类型,因此我们在存储时需要进行类型转换:

data = map[string][]byte{

"Port": []byte("8000"),

"Path": []byte("/tmp/go_reflection"),

"Debug": []byte("true"),

}

config := Config{}

i, err := strconv.ParseInt(string(data["Port"]), 10, 64)

if err == nil {

config.Port = i

}

对于int64类型我们需要strconv.ParseInt,对于string我们需要string(),对于bool我们需要strconv.ParseBool,在上面这个例子中我们可以通过switch来判断每个配置项需要进行的转换,但在实际的应用中配置可能多达数十项,继续使用这种原始的方法简直就是一个噩梦。这时我们需要通过Go的Reflect模块来帮助我们更优雅地解决这个问题。

反射(Reflection)作为元编程的一种形式,赋予了我们在运行时判断变量类型的能力。Go的reflect模块通过将数据封装在reflect.Type与reflect.Value中,提供了一系列方法让我们“动态”地去判断变量的类型:

var x = 2013

switch reflect.ValueOf(x).Kind() {

case reflect.Int:

fmt.Println("It's an int!")

case reflect.String:

fmt.Println("It's a string!")

}

在我们配置文件的例子中,我们可以根据每个配置的Key动态判断该进行何种类型转换并存储到那个属性中:

func (s *Config) Set(key string, value []byte) error {

reflectValue := reflect.ValueOf(s).Elem()

reflectField := reflectValue.FieldByName(key)

switch reflectField.Kind() {

case reflect.Int64:

i, err := strconv.ParseUint(string(value), 10, 64)

if err != nil {

return err

}

reflectField.SetInt(i)

case reflect.String:

reflectField.SetString(string(value))

case reflect.Bool

i, err := strconv.ParseBool(string(value))

if err != nil {

return err

}

reflectField.SetBool(i)

}

return nil

这样,我们只需要通过config.Set("Port", []byte("8000"))一个方法就可以正确地存储配置信息,就算结构定义有变更也不需要重写解析方法。关于反射Golang Blog上有一篇详细的文章The Laws of Reflection,可以更好地帮助我们Go中反射的细节与需要注意的地方,值得一读。

转载请注明:爱学习爱分享 » Reflection in Golang【Go语言中文网】

喜欢 (0)or分享 (0)