Golang实践:reflect

发布于 2017-09-06 · 本文总共 6532 字 · 阅读大约需要 19 分钟

Go语言提供了一种机制,在编译时不知道类型的情况下, 可更新变量、在运行时查看值、调用方法以及直接对它们的布局进行操作; 这种机制称为反射;

反射也让我们可以把类型当作头等值;

为什么使用反射

有时我们需要写一个函数有能力统一处理各种值类型的函数,而这些类型可能无法共享同一个接口; 也可能布局未知,或者这个类型在我们设计函数时还不存在;

func (p *pp) printArg(arg interface{}, verb rune) {
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString)
		default:
			p.badVerb(verb)
		}
		return
	}

	// Special processing considerations.
	// %T (the value's type) and %p (its address) are special; we always do them first.
	switch verb {
	case 'T':
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
	case 'p':
		p.fmtPointer(reflect.ValueOf(arg), 'p')
		return
	}

	// Some types can be done without reflection.
	switch f := arg.(type) {
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
		p.fmtFloat(f, 64, verb)
	case complex64:
		p.fmtComplex(complex128(f), 64, verb)
	case complex128:
		p.fmtComplex(f, 128, verb)
	case int:
		p.fmtInteger(uint64(f), signed, verb)
	case int8:
		p.fmtInteger(uint64(f), signed, verb)
	case int16:
		p.fmtInteger(uint64(f), signed, verb)
	case int32:
		p.fmtInteger(uint64(f), signed, verb)
	case int64:
		p.fmtInteger(uint64(f), signed, verb)
	case uint:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint8:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint16:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint32:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint64:
		p.fmtInteger(f, unsigned, verb)
	case uintptr:
		p.fmtInteger(uint64(f), unsigned, verb)
	case string:
		p.fmtString(f, verb)
	case []byte:
		p.fmtBytes(f, verb, "[]byte")
	case reflect.Value:
		// Handle extractable values with special methods
		// since printValue does not handle them at depth 0.
		if f.IsValid() && f.CanInterface() {
			p.arg = f.Interface()
			if p.handleMethods(verb) {
				return
			}
		}
		p.printValue(f, verb, 0)
	default:
		// If the type is not simple, it might have methods.
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
	}
}

/usr/local/go/src/fmt/print.go

reflect.Type和reflect.Value

reflect.TypeOf示例:

func GetType() {
	t := reflect.TypeOf(3)
	fmt.Println(t.String())
	fmt.Println(t)
    // 	=== RUN   TestGetType
	// int
	// int
}

reflect.TypeOf源码:

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

reflect.ValueOf示例:

func GetValue() {
	t := reflect.ValueOf(3)
	fmt.Println(t)
	fmt.Println(t.String())
}
=== RUN   TestGetValue
3
<int Value>

reflect.ValueOf源码:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}

	// TODO: Maybe allow contents of a Value to live on the stack.
	// For now we make the contents always escape to the heap. It
	// makes life easier in a few places (see chanrecv/mapassign
	// comment below).
	escapes(i)

	return unpackEface(i)
}

使用reflect.Value来设置值

func SetValue() {
	x := 2
	a := reflect.ValueOf(2)
	b := reflect.ValueOf(x)
	c := reflect.ValueOf(&x)
	d := c.Elem()

	fmt.Println(a, a.CanAddr())
	fmt.Println(b, b.CanAddr())
	fmt.Println(c, c.CanAddr())
	fmt.Println(d, d.CanAddr())

	d.Set(reflect.ValueOf(3))
	fmt.Println(x)
	d.SetInt(9)
	fmt.Println(x)
}
=== RUN   TestSetValue
2 false
2 false
0xc000016120 false
2 true
3
9

访问结构体字段标签

	valueInf := reflect.ValueOf(x)
	typeInf := reflect.TypeOf(x)

	if reflect.TypeOf(x).Kind() == reflect.Ptr {
		valueInf = reflect.ValueOf(x).Elem()
		typeInf = reflect.TypeOf(x).Elem()
	}

	// fmt.Println("num field of interface type: ", typeInf.NumField())
	for i := 0; i < typeInf.NumField(); i++ {

		fieldOfType := typeInf.Field(i)
		valueOfTypeInField := valueInf.FieldByName(fieldOfType.Name)

		if enum, ok := fieldOfType.Tag.Lookup("enum"); ok {
			if typeKind == reflect.String && valueOfTypeInField.String() != "" {
				enums := strings.Split(enum, ",")
				for _, value := range enums {
					if valueOfTypeInField.String() == value {
						return nil
					}
				}
				return NewError(EnumsNotSupport, fmt.Sprintf("Request body invalid: unsupport value (%s) of (%s)", valueOfTypeInField, fieldOfType.Name))
			}
		}
	}
	return
}

显示类型的方法

func GetMethod(x interface{}) {
	v := reflect.ValueOf(x)
	t := v.Type()

	fmt.Printf("type: %v\n", t)

	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("func: %s %s %s\n", t, t.Method(i).Name, methodType.String())
	}
}
// type: time.Duration
// func: time.Duration Hours func() float64
// func: time.Duration Microseconds func() int64
// func: time.Duration Milliseconds func() int64
// func: time.Duration Minutes func() float64
// func: time.Duration Nanoseconds func() int64
// func: time.Duration Round func(time.Duration) time.Duration
// func: time.Duration Seconds func() float64
// func: time.Duration String func() string
// func: time.Duration Truncate func(time.Duration) time.Duration

示例:参数检验

// ValidateParams base check parameters in request body
func ValidateParams(modelPointer interface{}) (err RespError) {
	glog.Infof("Validate request body parameters : , type is %s. \n", reflect.TypeOf(modelPointer))

	defer func() {
		errRe := recover()
		if errRe != nil {
			glog.Errorf("request body validate error: %+v, detail: %+v.", errRe, modelPointer)
			err = NewError(RequestBodyInvalid, fmt.Sprintf("request body invalid: %+v", errRe))
		}
	}()
	valueInf := reflect.ValueOf(modelPointer)
	typeInf := reflect.TypeOf(modelPointer)

	if reflect.TypeOf(modelPointer).Kind() == reflect.Ptr {
		valueInf = reflect.ValueOf(modelPointer).Elem()
		typeInf = reflect.TypeOf(modelPointer).Elem()
	}

	// fmt.Println("num field of interface type: ", typeInf.NumField())
	for i := 0; i < typeInf.NumField(); i++ {

		fieldOfType := typeInf.Field(i)
		valueOfTypeInField := valueInf.FieldByName(fieldOfType.Name)

		typeKind := fieldOfType.Type.Kind()

		if typeKind == reflect.Struct {
			err := ValidateParams(valueOfTypeInField.Interface())
			if err != nil {
				return err
			}
		} else if typeKind == reflect.String {
			err := ValidateBaseParams(fieldOfType, valueOfTypeInField)
			if err != nil {
				return err
			}
		}

		if enum, ok := fieldOfType.Tag.Lookup("enum"); ok {
			if typeKind == reflect.String && valueOfTypeInField.String() != "" {
				enums := strings.Split(enum, ",")
				for _, value := range enums {
					if valueOfTypeInField.String() == value {
						return nil
					}
				}
				return NewError(EnumsNotSupport, fmt.Sprintf("Request body invalid: unsupport value (%s) of (%s)", valueOfTypeInField, fieldOfType.Name))
			}
		}
	}
	return
}

注意事项

反射是一个功能和表达能力都很强大的工具,但应该谨慎使用它;

1.基于反射的代码是很脆弱的;
能导致编译器报类型错误的每种写法,在反射中都有一个对应的误用方法; 编译器在编译时会报告这个错误,而反射错误则要等到执行时才以崩溃的方式来报告; 而这可能是代码开始执行很久后才会发生的事;

确保反射的使用完整地封装在包里面,并且如果可能,在包的API中避免使用reflect.Value; 尽量使用特定的类型来确保输入是合法的值;

2.类型其实也算是某种形式的文档,而反射的相关操作则无法做静态类型检查; 所以大量使用反射的代码是很难理解的;

3.基于反射的函数会比为特定类型优化的函数慢一两个数量级;

go版本

go version go1.13.6 darwin/amd64

refs

《The Go Programming Language》Alan A.A, Donovan & Brian W.Kernighan

http://c.biancheng.net/golang/reflect/




本博客所有文章采用的授权方式为 自由转载-非商用-非衍生-保持署名 ,转载请务必注明出处,谢谢。
声明:
本博客欢迎转发,但请保留原作者信息!
博客地址:邱文奇(qiuwenqi)的博客;
内容系本人学习、研究和总结,如有雷同,实属荣幸!
阅读次数:

文章评论

comments powered by Disqus


章节列表