反射-遍历
这是我们反射四讲的第四讲,本次给大家讲解如何使用反射处理一些简单的遍历。
这一讲非常的简单,主要介绍如何遍历数组,切片,字符串,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 stringinput anywantRes []anywantErr 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
在遍历 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 stringinput anywantKeys []anywantValues []anywantErr 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
。
很感谢大家的观看,如果您有宝贵的建议或者想要去了解哪一方面得知识,我很高兴为您讲解。
结束,下期见。