反射(Reflection)
为什么需要反射
有时候需要知道未知类型的类型表达方式, 有时候需要获取类型信息, 进行判断进行不同的处理
reflect.Type
和 reflect.Value
reflect包中两个重要的类型.
-
reflect.Type
是一个接口, 表示一个Go类型 - 可由
reflect.TypeOf()
以reflect.Type
的类型返回某个interface{}
的动态类型信息
t := reflect.TypeOf(3) // t: a reflect.Type
fmt.Println(t.String()) // "int"
// reflect.Type满足fmt.Stringer接口
fmt.Println(t) // "int"
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File" not io.Writer
-
reflect.Value
可装载任意类型的值, 满足Stringer
接口,reflect.ValueOf()
返回一个Value
v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"
-
Value
的Type
方法返回reflect.Type
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
-
reflect.ValueOf()
的逆操作是reflect.Value.Interface()
: 返回一个interface{} ,其值是与Value相同的具体值
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
- 跟interface{}不同的是无需用type断言知道动态类型, 比如以下
format.Any
使用Kind
方法,得到有限的Kind进行处理
package format
import (
"reflect"
"strconv"
)
// Any formats any value as a string.
func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
}
// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}
display, 一个递归值打印器
聚合类型只打印了类型, 引用类型打印地址, 需要进一步精细化处理
Display("e", expr)
func Display(name string, x interface{}) {
fmt.Printf("Display %s (%T):\n", name, x)
display(name, reflect.ValueOf(x))
}
func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf("%s = invalid\n", path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf("%s[%s]", path,
formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
display(fmt.Sprintf("(*%s)", path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
display(path+".value", v.Elem())
}
default: // basic types, channels, funcs
fmt.Printf("%s = %s\n", path, formatAtom(v))
}
}
-
Slice
和Array
:Len()
返回数组元素个数,Index()
返回元素的reflect.Value
,越界会panic -
Struct
:NumField
返回结构体字段个数,Field(i)
返回第i字段reflect.Value
形式的值 -
Map
:MapKeys
返回一个reflect.Value
类型的slice, 对应map的keys, 遍历仍然是随机的,MapIndex(key)
返回key对应的值Value - 指针:
Elem
返回指针指向的变量, 依然是reflect.Value
类型, nil也是安全的, type此时为Invalid
类型, 可用IsNil()
事先判断 - 接口: 先
IsNil
判断, 然后用v.Elem()获取动态值
通过reflect.Value修改值
有一些reflect.Values是可取地址的, 这种是可以设置其值的
x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)
fmt.Println(d.CanAddr()) // "true"
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
d.Set(reflect.ValueOf(4))
- CanAddr方法来判断其是否可以被取地址
- 通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新
- Set方法将在运行时执行和编译时进行类似的可赋值性约束的检查
- Set方法:SetInt、SetUint、SetString和SetFloat等
- 反射机制不能修改未导出成员
- CanSet是用于检查对应的reflect.Value是否是可取地址并可被修改
- reflect.Zero函数将变量v设置为零值
x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // OK, x = 2
rx.Set(reflect.ValueOf(3)) // OK, x = 3
rx.SetString("hello") // panic: string is not assignable to int
rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int
var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2) // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
获取结构体字段标签
reflect.Type
的Field()
将返回一个reflect.StructField
, 含有每个成员的名字、类型和可选的成员标签等信息。其中成员标签信息对应reflect.StructTag
类型的字符串,并且提供了Get
方法用于解析和根据特定key提取的子串
// Unpack populates the fields of the struct pointed to by ptr
// from the HTTP request parameters in req.
func Unpack(req *http.Request, ptr interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
// Build map of fields keyed by effective name.
fields := make(map[string]reflect.Value)
v := reflect.ValueOf(ptr).Elem() // the struct variable
for i := 0; i < v.NumField(); i++ {
fieldInfo := v.Type().Field(i) // a reflect.StructField
tag := fieldInfo.Tag // a reflect.StructTag
name := tag.Get("http")
if name == "" {
name = strings.ToLower(fieldInfo.Name)
}
fields[name] = v.Field(i)
}
// Update struct field for each parameter in the request.
for name, values := range req.Form {
f := fields[name]
if !f.IsValid() {
continue // ignore unrecognized HTTP parameters
}
for _, value := range values {
if f.Kind() == reflect.Slice {
elem := reflect.New(f.Type().Elem()).Elem()
if err := populate(elem, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
f.Set(reflect.Append(f, elem))
} else {
if err := populate(f, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
}
}
}
return nil
}
func populate(v reflect.Value, value string) error {
switch v.Kind() {
case reflect.String:
v.SetString(value)
case reflect.Int:
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
v.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
v.SetBool(b)
default:
return fmt.Errorf("unsupported kind %s", v.Type())
}
return nil
}
显示一个类型的方法集
-
reflect.Type
和reflect.Value
都提供了Method
方法 - 每次
t.Method(i)
调用返回reflect.Method的实例 - 每次
v.Method(i)
调用都返回一个reflect.Value
以表示方法值 - 使用reflect.Value.Call方法调用一个Func类型的Value
// Print prints the method set of the value x.
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}
深度相等判断
reflect.DeepEqual
可以对两个值进行深度相等判断, 使用基础类型的==
判断, 会递归复合类型
func TestSplit(t *testing.T) {
got := strings.Split("a:b:c", ":")
want := []string{"a", "b", "c"};
if !reflect.DeepEqual(got, want) { /* ... */ }
}
使用反射的忠告
- 基于反射的代码脆弱, 运行时才会抛panic
- 不能做静态类型检查, 太多则可能难以理解
- 运行速度慢一到两个数量级, 测试适合使用, 性能关键路径避免使用
- 若非真正需要, 请不要使用reflect
Reference
有疑问加站长微信联系(非本文作者)