前景提要

在上一篇Reflection in golang的文章中,主要介绍了reflect.Type, reflect.Value, reflect.ValueOf, reflect.Type等数据结构以及接口。文章的开头我们想通过switch/case的方式来将任意类型的数据进行格式化操作。因为遇到数据类型太多,难以维护的问题,我们选择了反射当中的kind类型来解决。文章的最后给出了一个简单的demo来表明,通过reflect.Kind的帮助,我们可以将无限的数据类型归结到几个有限的基本数据类型中,使得我们的格式化操作变得比之前要简单得多。但是同样有一个问题就是,对于slice,array,map等这种复合型的数据类型的时候,我们没有办法递归的进行操作。只能简单的输出他们的类型或者是地址。这篇文章将会以此问题为起点,继续介绍reflect包中的种种特性。

Recursive display

先来看看我实现的递归输出任意类型内容的demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

func display(path string, v reflect.Value) {
	switch v.Kind() {
	case reflect.Invalid:
		fmt.Printf("%s = invalid\n", path)
	case reflect.Slice, reflect.Array:
		for i := 0; i < v.Len(); i++ {
			display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
		}
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			display(fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name), v.Field(i))
		}
	case reflect.Map:
		for _, key := range v.MapKeys() {
			display(fmt.Sprintf("%s[%s]", path, formatAtom(key)), v.MapIndex(key))
		}
	case reflect.Ptr:
		if v.IsNil() {
			fmt.Printf("%s = nil\n", path)
		} else {
			display(fmt.Sprintf("(*%s)", path), v.Elem())
		}
	case reflect.Interface:
		if v.IsNil() {
			fmt.Printf("%s = nil\n", path)
		} else {
			fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
			display(path+".Value", v.Elem())
		}
	default:
		fmt.Printf("%s = %s\n", path, formatAtom(v))
	}
}

func formatAtom(v reflect.Value) string {
	switch v.Kind() {
	case reflect.Int, reflect.Int64, reflect.Int8, reflect.Int32, reflect.Int16:
		return strconv.FormatInt(v.Int(), 10)
	case reflect.String:
		return strconv.Quote(v.String())
	case reflect.Bool:
		return strconv.FormatBool(v.Bool())
	case reflect.Chan, reflect.Func:
		return v.Type().String() + "0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
	default:
		return v.Type().String() + " value"

	}
}

type A struct {
	Domain     string
	StateSlice []string
	LineMap    map[string]int
	Ptr        *int
	Inter      interface{}
}

func main() {
	m := make(map[string]int)
	m["fuck"] = 2
	m["you"] = 1
	p := 3
	i := 4
	a := A{
		Domain:     "xuran.qbox.net",
		StateSlice: []string{"a", "b", "c", "d"},
		LineMap:    m,
		Ptr:        &p,
		Inter:      i,
	}

	display("a", reflect.ValueOf(a))
	return
}

运行结果如下

1
2
3
4
5
6
7
8
a.StateSlice[1] = "b"
a.StateSlice[2] = "c"
a.StateSlice[3] = "d"
a.LineMap["fuck"] = 2
a.LineMap["you"] = 1
(*a.Ptr) = 3
a.Inter.type = int
a.Inter.Value = 4

实现的逻辑还是非常简单的,但是使用golang的反射一定要小心小心再小心,因为reflect.Value虽然有很多方法可以调用,但是并不是每个都像String方法那么友好,对于内部包含了某种特定动态类型的reflect.Value值使用一些不恰当的方法的时候,是很有可能会直接panic的。我实现的版本中在调用特定的方法之前都利用kind()配合switch/case检查了reflect.Value类型数据所包含的动态类型是否支持我要做的操作,比如display函数中对Slice类型单个元素的操作,如果我没有检查,对一个内置动态类型为string的reflect.Value数据使用了index方法,那么就会导致panic。文档中对于Index这个函数式这样描述的:

1
Index returns v's i'th element. It panics if v's Kind is not Array, Slice, or String or i is out of range.

对于reflect.Value类型,文档也有相应的描述

1
2
3
4
5
Value is the reflection interface to a Go value.
Not all methods apply to all kinds of values. Restrictions, 
if any, are noted in the documentation for each method.
Use the Kind method to find out the kind of value before
calling kind-specific methods. Calling a method inappropriate to the kind of type causes a run time panic.

可见,golang文档中对于reflect.Value方法的使用也倾向于先使用Kind来确定类型在操作这种形式。

在实现的时候,需要注意以下几点: 1. 在操作struct类型值的时候,numField可以返回包括匿名成员在内的所有成员数量。 2. mapKeys方法会返回一个map内key的集合,但是当我们迭代的时候,它的顺序是不一致的,这和map本身是hashMap实现是有关系的。用formatAtom来处理map的key的时候其实是有一些问题,因为map的key的类型在golang中是可以使用一切可以比较的类型,如interface, array等都是合法的类型,但是上面的实现中暂时没有处理。 3. 处理指针类型的时候,v.ELem()返回的是一个该指针指向的变量。即使此时指针的值是nil,formatAtom的第一个case reflect.Invaild也会以合适的方式处理它,并且在实现中,已经提前检测了指针所指向的值是否为nil。

对递归实现的一些思考

reflect中的zero value和golang中普通的zero value值是不一样的, 一般来讲普通的zero value指的是特定类型的默认值,如int是0,string是”“。reflect.ValueOf的文档描述中,有这样的解释:

1
2
ValueOf returns a new Value initialized to the concrete value stored in the 
interface i. ValueOf(nil) returns the zero Value.

也就是说,ValueOf通过传进来的interface值内部的动态类型的值初始化创建一个reflect.Value,如果传递进来的interface的值本身就为nil,而不是interface内部动态类型值为nil,那么valueOf出来的结果就是reflect中的zero value.他的类型应该是Invalid。对于zero value的reflect.Value值,除了调用Kind和String两个方法之外,其余的方法都会造成panic。

测试代码如下

1
2
3
4
5
6
	var haha *int
	//haha = nil
	fmt.Println(reflect.ValueOf(haha).IsValid()) true
	fmt.Println(reflect.ValueOf(haha)) <nil>
	var hehe interface{}
	fmt.Println(reflect.ValueOf(hehe).IsValid() false

通过上面的代码就可以看出,valueof一个包含了指向nil的指针的interface值得到的reflect.Value是非zero value,但是valueof一个值为nil的interface变量就是 zero value。

仔细思考过上面代码的同学就可以看出一些额外的端倪,当我们在处理a.Inter的时候,看程序的执行结果是命中了reflect.Interface的case。但是假设我们现在写这样的两行代码:

1
2
	var ff interface{} = 3
	fmt.Println(reflect.Value(ff).Kind())

输出的结果却是int。返回来看之前valueOf函数的说明是可以得出一些线索的。valueof构造出来的reflect.Value和field(i)构造出来的reflect.Value调用kind的方法得出的结果并不是一致的。即使看起来两个reflect.Value都是通过一个interface的值构造出来的。虽然现在还不能明确搞清楚其中的原因,但是直觉上我觉得,valueof是通过interface包含的动态类型的值来构造reflect.Value的,虽然valueof接受的是一个Interface,但实际上却用了Interface内包含的3,field(4)返回的仅仅是Inter这个interface类型的变量。所以他的kind是reflect.Interface,如果再向下递归查找,就可以得出和上面一样的结果。

1
2
	fmt.Println(reflect.ValueOf(a).Field(4).Kind()) // interface
	fmt.Println(reflect.ValueOf(a).Field(4).Elem().Kind() // int

再举一个例子来说明上面的情况

1
2
3
4
5
6
	var i interface{} = 3
	fmt.Println(reflect.ValueOf(i).Kind()) //int
	
	fmt.Println(reflect.ValueOf(&i).Kind()) //ptr
	fmt.Println(reflect.ValueOf(&i).Elem().Kind()) //interface
	fmt.Println(reflect.ValueOf(&i).Elem().Elem().Kind()) //int

可见valueOf这个函数会像剥洋葱一样,一层一层的递归查找下去,而不是一下子就找到最底层的类型来创建reflect.Value类型的值。

本文代码

点这里