Golang笔记—反射
source link: https://www.tuicool.com/articles/yUbUVzn
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
反射(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
Recommend
-
121
不同语言的反射模式是不同的。 在golang的官网上有一篇文章很详细的介绍了go语言的反射机制: The Laws of Reflection。
-
49
github地址 作为静态语言,golang稍显笨拙,还好go的标准包 reflect (反射)包弥补了这点不足,它提供了一系列强大的API,能够根据执行过程中对象...
-
59
基本了解 在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个。你想要个 Struct type Foo struct { A int B string } 你想要一个值,你定义出来 v...
-
63
反射是指,在程序运行的时候,可以动态的检查自身的类型,结构和其他信息。 要了解Go语言的反射,我们需要了解两个概念。 reflect.Type reflect.Value 我们想要在运行时知道类型和值,怎么办呢?很明显就...
-
48
golang反射理论基础 反射就是动态的获取对象的信息,动态的执行对象的方法,为什么不直接获取对象的属性呢?为什么不直接调用他的方法呢?因为有时候不知道这个对象具体是什么类型,具体有哪些属性和方法。 golang中的反射和类...
-
37
在 golang中interface底层分析 文中分析了接口的底层原理。其中接口的内部结构分两种一种是iface接口,就是有方法的接口,另一种是eface是空接口。不管是哪种都有两个字...
-
61
反射这个概念绝大多数语言都有,比如Java,PHP之类,golang自然也不例外,反射其实程序能够自描述和自控制的一类机制。 比如,通过PHP的反射,你可以知道一个类有什么成员,有什么方法。而golang,也能够通过官方自带的reflect...
-
44
Go 语言反射的三大法则,其中包括: 从 interface{} 变量可以反射出反射对象; 从反射对象可以获取 interface{} 变量; 要修改反射对象,其值必须可设置; 从反射对象到接口值的过程就是...
-
11
golang 用反射reflect操作结构体
-
8
本文主要为了在对golang反射学习后做一个小练习,使用100行代码实现一个通用的RPC服务。 golang 的RPC框架还是非常丰富的,比如 gRPC,go-zero, go-dubbo 等都是使用非常普遍的rpc框架。在go语言实现的RPC客户端中,大部分RPC框架采用的是使用生成代码...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK