分享
  1. 首页
  2. 文章

Golang笔记—反射

猎奇师 · · 1283 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

反射(Reflection)

为什么需要反射

有时候需要知道未知类型的类型表达方式, 有时候需要获取类型信息, 进行判断进行不同的处理

reflect.Typereflect.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>"
  • ValueType方法返回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))
 }
}
  • SliceArray: 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.TypeField()将返回一个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.Typereflect.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


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:猎奇师

查看原文:Golang笔记—反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1283 次点击
1 回复 | 直到 2025年05月02日 09:17:33
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏