1.切片转换
日常开发我们可能需要一个类型的切片转换为另一个类型的切片。
比如 int[] 转为 []string。
代码语言:javascript复制import "strconv"
func IntSliceToStrSlice(s []int) []string {
if s == nil {
return nil
}
r := make([]string, len(s))
for i, v := range s {
r[i] = strconv.Itoa(v)
}
return r
}
再如 []float64 转为 []string。
代码语言:javascript复制func Float64SliceToStrSlice(s []float64) []string {
if s == nil {
return nil
}
r := make([]string, len(s))
for i, v := range s {
r[i] = strconv.FormatFloat(v, 'f', -1, 64)
}
return r
}
上面的两个例子,除了元素类型转换的实现不同,其他的代码都是重复的。如果为多种不同类型切片互转都实现各自的转换函数,无疑是低效繁琐的。
2.反射
实际上,利用 Golang 反射,可以为目标类型切片的转换只写一个函数。比如,可以接收任意类型切片,将其转换为 []string。
代码语言:javascript复制// ToStrSliceE converts an any type value to a []string with returned error.
func ToStrSliceE(a any) ([]string, error) {
if a == nil {
return nil, nil
}
switch v := i.(type) {
case []string:
return v, nil
}
// If a is a slice.
kind := reflect.TypeOf(a).Kind()
if kind == reflect.Slice || kind == reflect.Array {
sl := reflect.ValueOf(a)
s := make([]string, sl.Len())
for i := 0; i < sl.Len(); i {
v, err := ToStringE(sl.Index(i).Interface())
if err != nil {
return nil, err
}
s[i] = v
}
return s, nil
}
return nil, fmt.Errorf("unable to cast %#v of type %T to []string", a, a)
}
其中 ToStringE 是一个将任意类型转换为 string 的函数,其实现如下:
代码语言:javascript复制// ToStringE casts any type to a string type.
func ToStringE(i any) (string, error) {
i = indirectToStringerOrError(i)
switch s := i.(type) {
case string:
return s, nil
case bool:
return strconv.FormatBool(s), nil
case int:
return strconv.Itoa(s), nil
case int64:
return strconv.FormatInt(s, 10), nil
case int32:
return strconv.Itoa(int(s)), nil
case int16:
return strconv.FormatInt(int64(s), 10), nil
case int8:
return strconv.FormatInt(int64(s), 10), nil
case uint:
return strconv.FormatUint(uint64(s), 10), nil
case uint64:
return strconv.FormatUint(uint64(s), 10), nil
case uint32:
return strconv.FormatUint(uint64(s), 10), nil
case uint16:
return strconv.FormatUint(uint64(s), 10), nil
case uint8:
return strconv.FormatUint(uint64(s), 10), nil
case float64:
return strconv.FormatFloat(s, 'f', -1, 64), nil
case float32:
return strconv.FormatFloat(float64(s), 'f', -1, 32), nil
case json.Number:
return s.String(), nil
case []byte:
return string(s), nil
case template.HTML:
return string(s), nil
case template.HTMLAttr:
return string(s), nil
case template.URL:
return string(s), nil
case template.JS:
return string(s), nil
case template.JSStr:
return string(s), nil
case template.CSS:
return string(s), nil
case template.Srcset:
return string(s), nil
case nil:
return "", nil
case fmt.Stringer:
return s.String(), nil
case error:
return s.Error(), nil
default:
return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i)
}
}
使用反射,可以避免为不同类型转为指定类型切片书写多个相似的转换函数,一定程度上实现了泛化。但其也有不足之处。
- 反射涉及到运行时类型的判断,有一定性能开销,性能会降低 20% 左右。
- 如果转换为不同的目标类型切片,仍需要编写多个不同的转换函数。
3.泛型
Golang 在 1.18 中引入了泛型。利用泛型,我们可以不用针对不同的目标类型切片单独实现转换函数,真正做到一个函数,完成所有类型切片间的转换。
代码语言:javascript复制// ToSliceE converts any type slice or array to the specified type slice.
// An error will be returned if an error occurred.
func ToSliceE[T any](a any) ([]T, error) {
if a == nil {
return nil, nil
}
switch v := a.(type) {
case []T:
return v, nil
case string:
return ToSliceE[T](strings.Fields(v))
}
kind := reflect.TypeOf(a).Kind()
switch kind {
case reflect.Slice, reflect.Array:
// If input is a slice or array.
v := reflect.ValueOf(a)
if kind == reflect.Slice && v.IsNil() {
return nil, nil
}
s := make([]T, v.Len())
for i := 0; i < v.Len(); i {
val, err := ToAnyE[T](v.Index(i).Interface())
if err != nil {
return nil, err
}
s[i] = val
}
return s, nil
default:
// If input is a single value.
v, err := ToAnyE[T](a)
if err != nil {
return nil, err
}
return []T{v}, nil
}
}
如果不关注错误,可以忽略。
代码语言:javascript复制// ToSlice converts an any type slice to the specified type slice.
func ToSlice[E, T any, S ~[]E](s S) []T {
r, _ := ToSliceE[E, T](s)
return r
}
注意,不同类型元素之间的转换,依赖 ToAnyE 函数,其实现如下:
代码语言:javascript复制// ToAnyE converts one type to another and returns an error if error occurred.
func ToAnyE[T any](a any) (T, error) {
var t T
switch any(t).(type) {
case bool:
v, err := ToBoolE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int:
v, err := ToIntE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int8:
v, err := ToInt8E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int16:
v, err := ToInt16E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int32:
v, err := ToInt32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case int64:
v, err := ToInt64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint:
v, err := ToUintE(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint8:
v, err := ToUint8E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint16:
v, err := ToUint16E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint32:
v, err := ToUint32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case uint64:
v, err := ToUint64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case float32:
v, err := ToFloat32E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case float64:
v, err := ToFloat64E(a)
if err != nil {
return t, err
}
t = any(v).(T)
case string:
v, err := ToStringE(a)
if err != nil {
return t, err
}
t = any(v).(T)
default:
return t, fmt.Errorf("the type %T isn't supported", t)
}
return t, nil
}
将不同类型的元素转换为不同目标类型时,仍需要单独实现其转换函数,因为这里的转换逻辑是不同的。
具体类型值之间的转换函数就不贴了,详见 dablelv/cyan。
4.dablelv/cyan
为了方便大家使用,以上相关代码已开源至 Github 工具库 dablelv/cyan,大家可 import 后使用,欢迎大家 star 和 pr。
代码语言:javascript复制import (
"fmt"
"github.com/dablelv/cyan/conv"
)
func main() {
s1 := []int{1, 2, 3}
s2 := []float64{1.1, 1.2, 1.30}
s3 := []string{"1", "2", "3.00"}
fmt.Println(conv.ToSliceE[string](s1))
fmt.Println(conv.ToSliceE[string](s2))
fmt.Println(conv.ToSliceE[int](s3))
// and so on...
}
运行输出:
代码语言:javascript复制[1 2 3] <nil>
[1.1 1.2 1.3] <nil>
[1 2 3] <nil>
参考文献
github.com/dablelv/cyan