golang中通过反射给对象赋值 reflect.Value,reflect.Type, 自定义异常 最佳实践

在go语言的开发中,特别是框架的开发中,我们经常用到通过反射来给对象赋值(修改对象的值),这个知识点是作为一个go开发者所必备的技能。 我们要掌握这个技能,首先要搞清楚 go语言的反射reflect包里面的这2个对象,一个是 reflect.Value 类型 【通常使用 reflect.ValueOf函数获取】,另外一个是 reflect.Type 接口定义【通常使用 reflect.TypeOf函数获取】,这2个初学者非常容易混淆,大家一定要搞清楚,Value是类型定义, 他实现了Type接口**,而Type是接口定义**。 go语言中类型定义和接口定义可不是一个东西,这点要特别注意!

反射赋值最佳实践

下面通过一个最简单的示例来演示反射赋值和自定义异常的实际应用。

Go 复制代码
// 自定义异常
var ErrCacheMiss,ErrNotStored error

// 用来保存数据的map对象定义
var myData=make(map[string]interface{})

// go反射修改对象的值示例 
// 使用 CacheGet("xxx", &val) 这里的 &val就是你自己定义的用来接收要获取的值的变量的指针
func CacheGet(key string, dest interface{}) error {
   // 从我们定义的myData中获取数据
	val, ok := myData[key]
	if !ok {
		return ErrCacheMiss
	}

	// 先通过反射的ValueOf方法获取reflect.Value对象
	rv := reflect.ValueOf(dest)
	// rv.Type()获取rv对应的类型(reflect.Type),在通过类型中的方法.Kind()获取类型对应的类别, 这里的类别包含go中的所有类型,详见go源码src/reflect/type.go 中的常量定义
	// 这里的 rv.Elem() 是获取rv对应的实际的值,注意这里因为rv对象的值 dest 的类型是一个指针(reflect.Ptr),所以此处必须使用.Elem()方法获取指针指向的值后才可进行数据的修改操作。
	if rv.Type().Kind() == reflect.Ptr && rv.Elem().CanSet() {
		rv.Elem().Set(reflect.ValueOf(val)) // 将获取到的值通过反射赋值给dest
		return nil
	}
	return ErrNotStored
}

说明:上面示例中我们在使用的时候 dest 必须是指针类型, 另外关于 .Elem()方法的使用新手非常容易混淆(因为在你获取Value后,不管你是否调用Elem方法,当前对象所拥有的方法都一样 ),Elem()的作用是获取指针对应的值。 注意,如果你的dest的类型是指针 ,则rv就必须要先调用Elem()获取对应的值后再进行后续操作,否则不生效(因为你当前操作的对象不对)。

自定义异常 error

go里面的自定义异常非常简单, 你直接自定义一个error的类型即可,见上面的示例。

在go里面,error是一个内置的接口定义,他就定义了一个方法Error() string, 所以,在go语言里面,其实只要你的对象只要是实现了方法 Error() string 则你的这个对象就可以作为一个自定义的异常来使用。

error接口定义参考

The error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error.

reflect.Value类型定义参考

reflect.Value实现了reflect.Type中定义的所有方法,是Type接口的具体实现。

Go 复制代码
type Value struct {
	// contains filtered or unexported fields
}

reflect.Type接口interface定义参考

这个接口定义了很多的方法,而我们最常用的应该就是 Kind方法了,他返回的是当前操作对象的反射数据类型,这些类型都以常量的方式定义在了 src/reflect/type.go 中

Go 复制代码
type Type interface {

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver,
	// and only exported methods are accessible.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Methods are sorted in lexicographic order.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of methods accessible using Method.
	//
	// For a non-interface type, it returns the number of exported methods.
	//
	// For an interface type, it returns the number of exported and unexported methods.
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string

	// PkgPath returns a defined type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or not defined (*T, struct{},
	// []int, or A where A is an alias for a non-defined type), the package path
	// will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind

	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

	// ConvertibleTo reports whether a value of the type is convertible to type u.
	// Even if ConvertibleTo returns true, the conversion may still panic.
	// For example, a slice of type []T is convertible to *[N]T,
	// but the conversion will panic if its length is less than N.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	// Even if Comparable returns true, the comparison may still panic.
	// For example, values of interface type are comparable,
	// but the comparison will panic if their dynamic type is not comparable.
	Comparable() bool

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	// If the returned field is promoted from an embedded struct,
	// then Offset in the returned StructField is the offset in
	// the embedded struct.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any embedded structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing embedded fields.
	//
	// If the returned field is promoted from an embedded struct,
	// then Offset in the returned StructField is the offset in
	// the embedded struct.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type
	// contains filtered or unexported methods
}

Kind方法返回的可比较反射类型常量定义参考

注意下面的常量定义中的iota, 俗称IO塔, 它从0开始依次递增, 如下面的 reflect.Bool 就表示常量 1, reflect.Int 就表示常量2依次类推...

Go 复制代码
// A Kind represents the specific kind of type that a [Type] represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Pointer
	Slice
	String
	Struct
	UnsafePointer
)

// Ptr is the old name for the [Pointer] kind.
const Ptr = Pointer

iota定义参考

const iota = 0 // Untyped int.

iota is a predeclared identifier representing the untyped integer ordinal number of the current const specification in a (usually parenthesized) const declaration. It is zero-indexed.

相关推荐
一只爱打拳的程序猿7 分钟前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
杨荧8 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰15 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT27 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。29 分钟前
c++多线程
java·开发语言
小政爱学习!31 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
plmm烟酒僧1 小时前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv