Go是一种静态类型的语言。 在大多数情况下,变量的类型在编译时是已知的。接口类型是一种例外,尤其是空接口interface {}。
空接口是一种动态类型,类似于Java或C#中的Object。
在编译时,我们无法确定接口类型的基础值是整数还是字符串。
标准库中的reflect包,使我们可以在运行时使用此类动态值。 我们可以:
- 检查动态值的类型
- 枚举结构的字段
- 设定值
- 在运行时创建新值
用于在运行时检查接口值的类型的相关语言级功能是类型switch和类型断言:
var v interface{} = 4
var reflectVal reflect.Value = reflect.ValueOf(v)
var typ reflect.Type = reflectVal.Type()
fmt.Printf("Type '%s' of size: %d bytes\n", typ.Name(), typ.Size())
if typ.Kind() == reflect.Int {
fmt.Printf("v contains value of type int\n")
}
Type 'int' of size: 8 bytes
v contains value of type int
反射的基础是:
- 以空接口interface{}类型的值开头
- 使用reflect.ValueOf(v interface {})来获取reflect.Value,它代表有关该值的信息
- 使用reflect.Value检查值的类型,测试值是否为nil,设置值
反射有几种实际用途。
原始类型
让我们看看可以对基本类型(例如int或string)执行哪种操作。
获取类型
func printType(v interface{}) {
rv := reflect.ValueOf(v)
typ := rv.Type()
typeName := ""
switch rv.Kind() {
case reflect.Ptr:
typeName = "pointer"
case reflect.Int:
typeName = "int"
case reflect.Int32:
typeName = "int32"
case reflect.String:
typeName = "string"
// ... handle more cases
default:
typeName = "unrecognized type"
}
fmt.Printf("v is of type '%s'. Size: %d bytes\n", typeName, typ.Size())
}
printType(int32(3))
printType("")
i := 3
printType(&i) // *int i.e. pointer to int
v is of type 'int32'. Size: 4 bytes
v is of type 'string'. Size: 16 bytes
v is of type 'pointer'. Size: 8 bytes
在真实代码中,你需要处理关心的所有类型。
获取值
func getIntValue(v interface{}) {
var reflectValue = reflect.ValueOf(v)
n := reflectValue.Int()
fmt.Printf("Int value is: %d\n", n)
}
getIntValue(3)
getIntValue(int8(4))
getIntValue("")
Int value is: 3
Int value is: 4
panic: reflect: call of reflect.Value.Int on string Valuegoroutine 1 [running]:
reflect.Value.Int(...)
/usr/local/go/src/reflect/value.go:986
main.getIntValue(0x4a0120, 0x4db0d0)
/tmp/src282322283/main.go:14 +0x204
main.main()
/tmp/src282322283/main.go:24 +0x7b
exit status 2
为了最大程度地减少API表面,Int()返回int64并对所有带符号的整数值(int8,int16,int32,int64)有效。
UInt()方法返回uint64并对每个无符号整数值(uint8,uint16,uint32,uint64)有效。
试图从不兼容类型(如字符串)的值中获取整数值会引发panic。
为了避免出现panic,您可以先使用Kind()检查类型。
检索值的所有方法:
- Bool() bool
- Int() int64
- UInt() uint64
- Float() float64
- String() string
- Bytes() []byte
设置值
type S struct {
N int
}
func setIntPtr() {
var n int = 2
reflect.ValueOf(&n).Elem().SetInt(4)
fmt.Printf("setIntPtr: n=%d\n", n)
}
func setStructFieldDirect() {
var s S
reflect.ValueOf(&s.N).Elem().SetInt(5)
fmt.Printf("setStructFieldDirect: n=%d\n", s.N)
}
func setStructPtrField() {
var s S
reflect.ValueOf(&s).Elem().Field(0).SetInt(6)
fmt.Printf("setStructPtrField: s.N: %d\n", s.N)
}
func handlePanic(funcName string) {
if msg := recover(); msg != nil {
fmt.Printf("%s panicked with '%s'\n", funcName, msg)
}
}
func setStructField() {
defer handlePanic("setStructField")
var s S
reflect.ValueOf(s).Elem().Field(0).SetInt(4)
fmt.Printf("s.N: %d\n", s.N)
}
func setInt() {
defer handlePanic("setInt")
var n int = 2
rv := reflect.ValueOf(n)
rv.Elem().SetInt(4)
}
func setIntPtrWithString() {
defer handlePanic("setIntPtrWithString")
var n int = 2
reflect.ValueOf(&n).Elem().SetString("8")
}
setIntPtr: n=4
setStructFieldDirect: n=5
setStructPtrField: s.N: 6
setInt panicked with 'reflect: call of reflect.Value.Elem on int Value'
setStructField panicked with 'reflect: call of reflect.Value.Elem on struct Value'
setIntPtrWithString panicked with 'reflect: call of reflect.Value.SetString on int Value'
如setInt和setStructField所示,只有从指向值的指针开始,才可以更改值。
如setInt和setStructField所示,只有从指向值的指针开始,才可以更改值。
setStructPtrField显示如何通过字段值在结构中的位置来获取对字段值的引用。
尝试设置不兼容类型的值会引发panic。
设置值的方法反映了读取值的方法:
- SetBool(v bool)
- SetInt(v int64)
- SetUInt(v uint64)
- SetFloat(v float64)
- SetString(v string)
- SetBytes(v []byte)
指针
指向X的指针与X是不同的类型。
如果reflect.Value引用了指向值的指针,则可以通过调用Elem()获得对该值的引用。
func printIntResolvingPointers(v interface{}) {
rv := reflect.ValueOf(v)
typeName := rv.Type().String()
name := ""
for rv.Kind() == reflect.Ptr {
name = "pointer to " + name
rv = rv.Elem()
}
name += rv.Type().String()
fmt.Printf("Value: %d. Type: '%s' i.e. '%s'.\n\n", rv.Int(), name, typeName)
}
func main() {
n := 3
printIntResolvingPointers(n)
n = 4
printIntResolvingPointers(&n)
n = 5
np := &n
printIntResolvingPointers(&np)
}
Value: 3. Type: 'int' i.e. 'int'.
Value: 4. Type: 'pointer to int' i.e. '*int'.
Value: 5. Type: 'pointer to pointer to int' i.e. '**int'.
在底层,接口也是指向其基础值的指针,因此Elem()也可以在代表接口的reflect.Value上工作。
struct
列出struct的字段
使用反射,我们可以列出struct的所有字段。
type S struct {
FirstName string `my_tag:"first-name"`
lastName string
Age int `json:"age",xml:"AgeXml`
}
func describeStructSimple(rv reflect.Value) {
structType := rv.Type()
for i := 0; i < rv.NumField(); i++ {
v := rv.Field(i)
structField := structType.Field(i)
name := structField.Name
typ := structField.Type
tag := structField.Tag
jsonTag := tag.Get("json")
isExported := structField.PkgPath == ""
if isExported {
fmt.Printf("name: '%s',\ttype: '%s', value: %v,\ttag: '%s',\tjson tag: '%s'\n", name, typ, v.Interface(), tag, jsonTag)
} else {
fmt.Printf("name: '%s',\ttype: '%s',\tvalue: not accessible\n", name, v.Type().Name())
}
}
}
func main() {
s := S{
FirstName: "John",
lastName: "Doe",
Age: 27,
}
describeStructSimple(reflect.ValueOf(s))
}
name: 'FirstName', type: 'string', value: John, tag: 'my_tag:"first-name"', json tag: ''
name: 'lastName', type: 'string', value: not accessible
name: 'Age', type: 'int', value: 27, tag: 'json:"age",xml:"AgeXml', json tag: 'age'
使用反射,我们只能访问导出的字段的值(v.Interface {})。
导出的字段是名称以大写字母开头的字段(导出的是“姓氏”和“年龄”,而不是“ lastName”)。
如果reflect.StructField.PkgPath ==“”,则导出字段。
递归列出结构的字段
检查struct本质上是递归过程。
您必须追逐指针并递归到嵌入式struct中。
在实际程序中,使用反射检查struct是递归的。
type Inner struct {
N int
}
type S struct {
Inner
NamedInner Inner
PtrInner *Inner
unexported int
N int8
}
func indentStr(level int) string {
return strings.Repeat(" ", level)
}
// if sf is not nil, this is a field of a struct
func describeStruct(level int, rv reflect.Value, sf *reflect.StructField) {
structType := rv.Type()
nFields := rv.NumField()
typ := rv.Type()
if sf == nil {
fmt.Printf("%sstruct %s, %d field(s), size: %d bytes\n", indentStr(level), structType.Name(), nFields, typ.Size())
} else {
fmt.Printf("%sname: '%s' type: 'struct %s', offset: %d, %d field(s), size: %d bytes, embedded: %v\n", indentStr(level), sf.Name, structType.Name(), sf.Offset, nFields, typ.Size(), sf.Anonymous)
}
for i := 0; i < nFields; i++ {
fv := rv.Field(i)
sf := structType.Field(i)
describeType(level+1, fv, &sf)
}
}
// if sf is not nil, this is a field of a struct
func describeType(level int, rv reflect.Value, sf *reflect.StructField) {
switch rv.Kind() {
case reflect.Int, reflect.Int8:
// in real code we would handle more primitive types
i := rv.Int()
typ := rv.Type()
if sf == nil {
fmt.Printf("%stype: '%s', value: '%d'\n", indentStr(level), typ.Name(), i)
} else {
fmt.Printf("%s name: '%s' type: '%s', value: '%d', offset: %d, size: %d\n", indentStr(level), sf.Name, typ.Name(), i, sf.Offset, typ.Size())
}
case reflect.Ptr:
fmt.Printf("%spointer\n", indentStr(level))
describeType(level+1, rv.Elem(), nil)
case reflect.Struct:
describeStruct(level, rv, sf)
}
}
func main() {
var s S
describeType(0, reflect.ValueOf(s), nil)
}
struct S, 5 field(s), size: 40 bytes
name: 'Inner' type: 'struct Inner', offset: 0, 1 field(s), size: 8 bytes, embedded: true
name: 'N' type: 'int', value: '0', offset: 0, size: 8
name: 'NamedInner' type: 'struct Inner', offset: 8, 1 field(s), size: 8 bytes, embedded: false
name: 'N' type: 'int', value: '0', offset: 0, size: 8
pointer
name: 'unexported' type: 'int', value: '0', offset: 24, size: 8
name: 'N' type: 'int8', value: '0', offset: 32, size: 1
slice
使用反射读取切片
a := []int{3, 1, 8}
rv := reflect.ValueOf(a)
fmt.Printf("len(a): %d\n", rv.Len())
fmt.Printf("cap(a): %d\n", rv.Cap())
fmt.Printf("slice kind: '%s'\n", rv.Kind().String())
fmt.Printf("element type: '%s'\n", rv.Type().Elem().Name())
el := rv.Index(0).Interface()
fmt.Printf("a[0]: %v\n", el)
elRef := rv.Index(1)
fmt.Printf("elRef.CanAddr(): %v\n", elRef.CanAddr())
fmt.Printf("elRef.CanSet(): %v\n", elRef.CanSet())
elRef.SetInt(5)
fmt.Printf("a: %v\n", a)
len(a): 3
cap(a): 3
slice kind: 'slice'
element type: 'int'
a[0]: 3
elRef.CanAddr(): true
elRef.CanSet(): true
a: [3 5 8]
使用反射创建新切片
typ := reflect.SliceOf(reflect.TypeOf("example"))
// create slice with capacity 10 and length 1
rv := reflect.MakeSlice(typ, 1, 10)
rv.Index(0).SetString("foo")
a := rv.Interface().([]string)
fmt.Printf("a: %#v\n", a)
a: []string{"foo"}
reflect.Kind
Reflection.Value上的函数Kind()返回描述值类型的reflect.Kind类型。
这是所有可能的值:
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
反射用途
序列化为JSON,XML,SQL,protobufs
反射使实现通用JSON序列化/反序列化成为可能。
对于通用JSON序列化,我们可以枚举任意结构的字段,读取它们的字段并创建相应的JSON字符串。
对于一般的JSON反序列化,我们可以枚举任意结构的字段并根据已解析的JSON数据进行设置。
其他序列化格式(例如XML,YAML或协议缓冲区)也是如此。
反射使为SQL数据库定义通用API成为可能,因为我们可以将任意结构转换为SQL数据库可以理解的格式,并将从SQL数据库接收的数据放入任意结构。
使用Go函数扩展模板语言
由于能够在运行时调用任意函数,因此我们可以在文本/模板中为模板定义自定义函数。 我们向模板引擎注册Go函数。
然后,引擎可以在执行模板时在运行时调用这些函数。
编写与Go紧密集成的口译员
由于反射功能可以在运行时调用任意函数,因此JavaScript解释器可以轻松地扩展为用Go编写的其他函数。