这是我们反射四讲的第四讲,本次给大家讲解如何使用反射处理一些简单的遍历。
这一讲非常的简单,主要介绍如何遍历数组,切片,字符串,map。
其中,前三种逻辑一致,在遍历map时会有些许不同,不过不用担心,还是很简单的。
// Iterate 遍历数组,切片,或者字符串
func Iterate(input any) ([]any, error) {
val := reflect.ValueOf(input)
typ := val.Type()
kind := typ.Kind()
if kind != reflect.Array && kind != reflect.Slice && kind != reflect.String {
return nil, errors.New("input must be array, slice or string")
}
res := make([]any, 0, val.Len())
for i := 0; i < val.Len(); i++ {
ele := val.Index(i)
if kind == reflect.String {
res = append(res, string(ele.Interface().(uint8)))
} else {
res = append(res, ele.Interface())
}
}
return res, nil
}
测试:
func TestIterate(t *testing.T) {
testcases := []struct {
name string
input any
wantRes []any
wantErr error
}{
{
name: "slicex",
input: []int{1, 2, 3},
wantRes: []any{1, 2, 3},
},
{
name: "array",
input: [3]int{1, 2, 3},
wantRes: []any{1, 2, 3},
},
{
name: "string",
input: "hello",
wantRes: []any{"h", "e", "l", "l", "o"},
},
{
name: "invalid",
input: map[string]int{"a": 1},
wantErr: errors.New("input must be array, slicex or string"),
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
res, err := Iterate(tt.input)
assert.Equal(t, err, tt.wantErr)
assert.Equal(t, tt.wantRes, res)
})
}
}
在遍历 Map 时,为大家提供两种实现。
第一种:使用 MapRange
提供的迭代器进行遍历。
// IterateMapV1 遍历map
func IterateMapV1(input any) ([]any, []any, error) {
val := reflect.ValueOf(input)
if val.Kind() != reflect.Map {
return nil, nil, errors.New("input must be map")
}
l := val.Len()
keys := make([]any, 0, l)
values := make([]any, 0, l)
itr := val.MapRange()
for itr.Next() {
keys = append(keys, itr.Key().Interface())
values = append(values, itr.Value().Interface())
}
return keys, values, nil
}
第二种:使用 MapKeys
提供的 keys
切片进行遍历。
// IterateMapV2 遍历map
func IterateMapV2(input any) ([]any, []any, error) {
val := reflect.ValueOf(input)
if val.Kind() != reflect.Map {
return nil, nil, errors.New("input must be map")
}
l := val.Len()
keys := make([]any, 0, l)
values := make([]any, 0, l)
for _, key := range val.MapKeys() {
keys = append(keys, key.Interface())
v := val.MapIndex(key)
values = append(values, v.Interface())
}
return keys, values, nil
}
测试:
func TestIterateMap(t *testing.T) {
testcases := []struct {
name string
input any
wantKeys []any
wantValues []any
wantErr error
}{
{
name: "nil",
input: nil,
wantErr: errors.New("input must be map"),
},
{
name: "map",
input: map[string]int{"a": 1, "b": 2, "c": 3},
wantKeys: []any{"a", "b", "c"},
wantValues: []any{1, 2, 3},
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
keys, values, err := IterateMapV2(tt.input)
assert.Equal(t, err, tt.wantErr)
assert.Equal(t, tt.wantKeys, keys)
assert.Equal(t, tt.wantValues, values)
})
}
}
反射可以让我们去设计一些通用型的代码以及处理方式,并且提供了一种灵活的让程序去自己了解自己和改变自己的能力。但是,反射也有极大的缺点,有时候可能会导致程序崩溃,并且反射代码相对苦涩难懂,不易于理解等。
至此,反射四讲完美结束,我平时很喜欢使用 TDD 的流程去开发,所以为大家提供了测试程序。
如果大家想要有一个小项目去实战练习的话,我给大家推荐我之前在ORM层面上实现的元数据的操作,同时也可以帮你练习代码能力。这是项目地址:ORM元数据的设计
最后,总结一下,没有足够的测试,我们一般不要使用反射,因为反射充满了panic
。
很感谢大家的观看,如果您有宝贵的建议或者想要去了解哪一方面得知识,我很高兴为您讲解。
结束,下期见。