巴洛特烦恼 發表於 2026-1-4 09:44:45

Go语言反射使用及优缺点总结

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一:什么是反射</a></li><li><a href="#_label1">二:Go 语言反射</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">reflect.TypeOf()</a></li><li><a href="#_lab2_1_1">reflect.ValueOf()</a></li></ul><li><a href="#_label2">三:反射使用</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_2">值对象</a></li><ul class="third_class_ul"><li><a href="#_label3_2_2_0">获取 struct 反射值</a></li></ul><li><a href="#_lab2_2_3">类型对象</a></li><ul class="third_class_ul"><li><a href="#_label3_2_3_1">struct 反射类型</a></li><li><a href="#_label3_2_3_2">指针反射类型</a></li><li><a href="#_label3_2_3_3">反射获取 struct 方法</a></li><li><a href="#_label3_2_3_4">通过反射调用方法</a></li><li><a href="#_label3_2_3_5">通过反射设置值</a></li></ul></ul><li><a href="#_label3">四:反射的优缺点</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">五:共勉</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一:什么是反射</h2>
<p>反射可以认为是程序在运行时的一种能力,反射可以在程序运行时访问、检测和修改它本身状态,比如在程序运行时可以检查变量的类型和值,调用它们的方法,甚至修改它们的值。使用反射可以增加程序的灵活性,简单来说,反射就是程序在运行时能够检测自身和修改自身的一种能力。</p>
<p class="maodian"><a name="_label1"></a></p><h2>二:Go 语言反射</h2>
<p>对于很多的高级语言都实现了反射,像 Java,Python。在 Go 语言中,反射在 Go 语言内置的 reflect 包下实现。Go 语言中的反射建立在 Go 的类型系统之上,并且与接口密切相关。通过前面的学习我们知道 Go 语言的空接口包含类型 (Type) 和值 (Value) 两个部分,在反射里,也要用到类型 (Type) 和值 (Value)。</p>
<p>reflect 包中定义了 reflect.Type 和 reflect.Value,正好对应我们前面所说的Type 和 Value。要注意的是 reflect.Type 是一个接口而 reflect.Value 是一个具体的结构体。在 reflect.Type 接口中定义了很多跟类型相关的方法,而reflect.Value 则是绑定了很多跟值相关的方法。</p>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>reflect.TypeOf()</h3>
<p><strong>由于 reflect.Type 是一个接口</strong>,所以只有当某个类型实现了这个接口,我们才能获取到它的类型,同时,在 reflect 包内,类型描述符是未导出类型,<strong>所以我们只能通过 reflect.TypeOf() 方法获取 reflect.Type 类型的值。</strong></p>
<p>我们首先看一个例子,看下 reflect.TypeOf() 的常用用法:</p>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string
    Ageint
}

func main() {
    var num int64 = 100
    t1 := reflect.TypeOf(num)
    fmt.Println(t1.String())

    st := Student{
       Name: "zhangsan",
       Age:18,
    }
    t2 := reflect.TypeOf(st)
    fmt.Println(t2.String())
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>int64<br />main.Student</p></blockquote>
<p>可以看到对于基础类型和 struct 类型通过调用 reflect.TypeOf() 都打印出了对应的类型信息。注意 reflect.TypeOf 返回的是一个 reflect.Type 接口类型,我们通过调用这个接口的 String() 方法,得到最终的字符串信息。</p>
<p>在前面学习 interface 的章节中,我们知道一个具体的数据类型是可以赋值给一个 interface 类型的,反过来则不行,要用到 interface 的断言。在一个interface 赋值之后,其实是对应了两个类型,一个是静态类型,就是在程序编译期就确定的类型,interface 的静态类型就是接口 interface,同时当 interface 赋值之后,他还有一个动态类型,就是被赋值的那个数据的具体类型,假设在上例中,我们将 st 赋值给一个空 interface,那么这个 interface 的动态类型就是 Student。</p>
<p>对一个数据对象进行反射操作,其实是首先将具体对象类型转化为一个 interface 类型,然后再将 interface 类型转化为 reflect 包下的反射类型,反射类型里的类型信息和值信息其实就是对应着这个中间类型 interface 的类型和值。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010409355687.png" /></p>
<p>reflect.TypeOf() 方法获取的就是这个 interface{} 中的类型部分。</p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>reflect.ValueOf()</h3>
<p>同理,reflect.ValueOf() 方法自然就是获取接口中的值部分,reflect.ValueOf() 的返回值其实就是一个 reflect.Value 结构体。</p>
<div class="jb51code"><pre class="brush:go;">import (
    "fmt"
    "reflect"
)

type Student struct {
    Name string
    Ageint
}

func main() {
    var num int64 = 100
    v1 := reflect.ValueOf(num)
    fmt.Println(v1)
    fmt.Println(v1.String())

    st := Student{
       Name: "zhangsan",
       Age:18,
    }
    v2 := reflect.ValueOf(st)
    fmt.Println(v2)
    fmt.Println(v2.String())
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>100<br />&lt;int64 Value&gt;<br />{zhangsan 18}<br />&lt;main.Student Value&gt;</p></blockquote>
<p>注意到这里 fmt.Println(v1) 和 fmt.Println(v1.String()) 打印的不一样,上面说了 reflect.ValueOf() 的返回值就是一个 reflect.Value 结构,但是 fmt.Println(v1) 却打印出了具体的值,这是因为 fmt.Println 的参数是一个接口类型,在执行过程中有一些类型转换,对 reflect.Value 结构做了特殊处理。</p>
<p class="maodian"><a name="_label2"></a></p><h2>三:反射使用</h2>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>值对象</h3>
<p>reflect 包下跟值对象相关的常用函数或方法:</p>
<table><thead><tr><th>函数/方法</th><th>说明</th></tr></thead><tbody><tr><td>reflect.TypeOf()</td><td>获取某个对象的反射类型实现 (reflect.Type)</td></tr><tr><td>reflect.ValueOf()</td><td>获取某个对象的反射值对象(reflect.Value)</td></tr><tr><td>reflect.Value.NumField()</td><td>获取结构体的反射值对象中的字段个数,只对结构体类型有效</td></tr><tr><td>reflect.Value.Field(i)</td><td>获取结构体的反射值对象中的第i个字段,只对结构体类型有效</td></tr><tr><td>reflect.Kind()</td><td>从反射值对象中获取该值的种类</td></tr><tr><td>reflect.Value.MapKeys()</td><td>对 map 的每个键的 reflect.Value 对象组成的一个切片</td></tr><tr><td>reflect.Value.MapIndex(i)</td><td>根据map的某个键的reflect.Value对象,返回值的reflect.Value对象</td></tr><tr><td>reflect.Value.Len()</td><td>对切片或数组的反射对象求切片或数组的长度</td></tr><tr><td>reflect.Value.Index(i)</td><td>返回切片或数组第i个元素的reflect.Value值</td></tr><tr><td>reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool()</td><td>从反射的值对象中取出对应值,注意 reflect.Int()/reflect.Uint()方法对种类做了合并处理,它们只返回相应的最大范围的类型,Int()返回Int64类型,Uint()返回Uint64类型</td></tr></tbody></table>
<p class="maodian"><a name="_label3_2_2_0"></a></p><h4>获取 struct 反射值</h4>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Namestring
    Age   int
    Score float64
}

func main() {

    st := Student{
       Name:"zhangsan",
       Age:   18,
       Score: 95.5,
    }
    v := reflect.ValueOf(st)
    fmt.Printf("the field num of Student is %d\n", v.NumField())
    fmt.Printf("field1 type is %v, value is %s\n", v.Field(0).Type().Name(), v.Field(0).String())
    fmt.Printf("field2 type is %v, value is %d\n", v.Field(1).Type().Name(), v.Field(1).Int())
    fmt.Printf("field2 type is %v, value is %f\n", v.Field(2).Type().Name(), v.Field(2).Float())
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>the field num of Student is 3<br />field1 type is string, value is zhangsan<br />field2 type is int, value is 18<br />field2 type is float64, value is 95.500000</p></blockquote>
<p>v := reflect.ValueOf(st),v是一个 Student 类型的反射值对象,通过 v.NumField() 可以得出 Student 类型的字段个数,然后 v.Field(i).Type().Name() 打印出各个字段值的类型,v.Field(i) 打印出各个字段值</p>
<p>注意:NumField() 和 Field() 方法只有原对象是结构体时才能调用,否则会 panic</p>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>类型对象</h3>
<table><thead><tr><th>函数/方法</th><th>说明</th></tr></thead><tbody><tr><td>reflect.Type.NumField()</td><td>获取结构体的反射类型对象中的字段个数,只对结构体类型有效</td></tr><tr><td>reflect.Type.Field(i)</td><td>获取结构体的反射类型对象中的第i个字段,只对结构体类型有效</td></tr><tr><td>reflect.Type.Elem()</td><td>根据指针获取对应的具体类型</td></tr><tr><td>reflect.Type.NumIn()</td><td>获取函数反射类型的参数个数</td></tr><tr><td>reflect.Type.In(i)</td><td>获取函数反射类型的第i个参数</td></tr><tr><td>reflect.Type.NumOut()</td><td>获取函数反射类型的返回值个数</td></tr><tr><td>reflect.Type.Out(i)</td><td>获取函数反射类型的第i个返回值</td></tr><tr><td>reflect.Type.NumMethod()</td><td>获取struct上绑定的方法个数</td></tr><tr><td>reflect.Type.Method(i)</td><td>获取struct上绑定的第i个方法</td></tr></tbody></table>
<p class="maodian"><a name="_label3_2_3_1"></a></p><h4>struct 反射类型</h4>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Namestring
    Age   int
    Score float64
}

func main() {
    st := Student{
       Name:"zhangsan",
       Age:   18,
       Score: 90.5,
    }
    t := reflect.TypeOf(st)
    fmt.Println(t.Name())
    fmt.Println(t.Kind())
    fmt.Println(t.NumField())
    for i := 0; i &lt; t.NumField(); i++ {
       fmt.Printf("field1 name is %s, field1 type is %s\n", t.Field(i).Name, t.Field(i).Type.String())
    }
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>Student<br />struct<br />3<br />field1 name is Name, field1 type is string</p></blockquote>
<p>通过 reflect.Type 的 Name() 方法可以获取对应的 Type 类型,Kind() 方法获取底层的数据种类,即 kind,跟 reflect.Value 一样,reflect.Type 也提供了 NumField() 方法用于获取结构体对象中的字段个数,通过 t.Field(i).Name 可以获取对应字段的名字。同样,Field(i) 和 NumField() 也只能对结构体反射使用</p>
<p class="maodian"><a name="_label3_2_3_2"></a></p><h4>指针反射类型</h4>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Namestring
    Age   int
    Score float64
}

func main() {
    st := &amp;Student{
       Name:"zhangsan",
       Age:   18,
       Score: 90.5,
    }
    t := reflect.TypeOf(st)

    fmt.Println(t.Kind())
    fmt.Println(t.Elem().Name())   // 这里一定要加Elem(),根据指针获取到具体类型后,才能或者具体的type名
    fmt.Println(t.Elem().NumField()) // 这里一定要加Elem(),根据指针获取到具体类型后,才能字段个数
    for i := 0; i &lt; t.Elem().NumField(); i++ {
       fmt.Printf("field1 name is %s, field1 type is %s\n", t.Elem().Field(i).Name, t.Elem().Field(i).Type.String())
    }

}
</pre></div>
<p>运行结果:</p>
<blockquote><p>ptr<br />Student<br />3<br />field1 name is Name, field1 type is string<br />field2 name is Age, field2 type is int<br />field3 name is Score, field3 type is float64</p></blockquote>
<p>可以看到,跟上面直接获取struct有一点点小小的区别,那就是fmt.Println(t.Kind())打印出的是一个ptr指针类型,而不再是struct类型,正是因为这里是一个ptr,所以我们不能直接在这个ptr上调用.Name()以及其他的.NumField()之类的方法,要根据ptr的.Elem()获取到具体类型之后,才能用这些方法,否则程序就回报panic,这点一定要注意</p>
<p class="maodian"><a name="_label3_2_3_3"></a></p><h4>反射获取 struct 方法</h4>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Namestring
    Age   int
    Score float64
}

func (s *Student) GetName() string {
    return s.Name
}

func (s *Student) SetName(name string) {
    s.Name = name
}

func (s *Student) GetAge() int {
    return s.Age
}

func (s *Student) SetAge(age int) {
    s.Age = age
}

func (s *Student) GetScore() float64 {
    return s.Score
}

func (s *Student) SetScore(score float64) {
    s.Score = score
}

func main() {
    st := &amp;Student{
       Name:"zhangsan",
       Age:   18,
       Score: 90.5,
    }
    t := reflect.TypeOf(st)

    for i := 0; i &lt; t.NumMethod(); i++ {
       m := t.Method(i)
       fmt.Printf("%+v\n", m)
    }
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>{GetName func(*main.Student) string}<br />{SetName func(*main.Student, string)}<br />{GetAge func(*main.Student) int}<br />{SetAge func(*main.Student, int)}<br />{GetScore func(*main.Student) float64}<br />{SetScore func(*main.Student, float64)}</p></blockquote>
<ul><li>reflect.Type.NumMethod():返回struct所绑定的的方法个数</li><li>reflect.Type.Method(i):返回第i个方法的 reflect.Method 对象</li></ul>
<p>reflect.Method 定义在 src/reflect/type.go 文件:</p>
<div class="jb51code"><pre class="brush:go;">type Method struct {
Name    string // 方法名
PkgPath string
TypeType// 方法类型(
FuncValue // 方法值(方法的接收器作为第一个参数)
Index int   // 是结构体中的第几个方法
}
</pre></div>
<p>所以,通过 reflect.Method 对象,我们可以获取到 struct 所绑定的对应方法的方法名,方法类型等信息</p>
<p class="maodian"><a name="_label3_2_3_4"></a></p><h4>通过反射调用方法</h4>
<p>在上一小节我们知道了 reflect.Type.Method(i) 可以获取到 struct 所绑定的具体的方法对象 reflect.Method,通过这个对象,我们不仅可以获取方法的详细信息,还可以动态的调用方法。</p>
<p>其实在 reflect.Value 里我们也可以使用 NumMethod()/Method(i) 方法获取到对应的方法信息,不同的是 reflect.Value.Method(i) 返回的使一个 reflect.Value 对象,但是同样可以根据这个对象来动态调用方法,只是两者调用方法的方式有所区别</p>
<p>请看具体例子:</p>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    Namestring
    Age   int
    Score float64
}

func (s *Student) GetName() string {
    return s.Name
}

func (s *Student) SetName(name string) {
    s.Name = name
}

func (s *Student) GetAge() int {
    return s.Age
}

func (s *Student) SetAge(age int) {
    s.Age = age
}

func (s *Student) GetScore() float64 {
    return s.Score
}

func (s *Student) SetScore(score float64) {
    s.Score = score
}

func main() {
    st := &amp;Student{
       Name:"zhangsan",
       Age:   18,
       Score: 90.5,
    }
    fmt.Printf("st === %+v\n", st)

    t := reflect.TypeOf(st)
    v := reflect.ValueOf(st)

    m1, ok := t.MethodByName("SetName")// 获取SetName方法
    fmt.Printf("t get func by name:%t\n", ok)

    argsV1 := make([]reflect.Value, 0)
    argsV1 = append(argsV1, v)
    argsV1 = append(argsV1, reflect.ValueOf("lisi"))
    m1.Func.Call(argsV1)       //
    fmt.Printf("st === %+v\n", st)

    m2 := v.MethodByName("SetName")    // 获取SetName方法
    argsV2 := make([]reflect.Value, 0)
    argsV2 = append(argsV2, reflect.ValueOf("wangwu"))
    m2.Call(argsV2)
    fmt.Printf("st === %+v\n", st)
}
</pre></div>
<p>运行结果:</p>
<blockquote><p>st === &amp;{Name:zhangsan Age:18 Score:90.5}<br />t get func by name:true<br />st === &amp;{Name:lisi Age:18 Score:90.5}<br />st === &amp;{Name:wangwu Age:18 Score:90.5}</p></blockquote>
<p>可以看到通过 reflect.Type.MethodByName() 方法获取到的reflect.Method对象和reflect.Value.MethodByName()方法获取到的reflect.Method获取到的reflect.Value对象都可以在程序运行时动态的调用方法修改结构本身,student的name由zhangsan------&gt;lisi------&gt;wangwu。</p>
<p>但是二者的调用存在一个区别:通过reflect.Method调用方法,必须使用Func字段,而且要传入接收器的reflect.Value作为第一个参数</p>
<div class="jb51code"><pre class="brush:go;">m1.Func.Call(argsV1)
</pre></div>
<p>reflect.Value.MethodByName()返回一个reflect.Value对象,它不需要接收器的reflect.Value作为第一个参数,而且直接使用Call()发起方法调用:</p>
<div class="jb51code"><pre class="brush:go;">m2.Call(argsV2)
</pre></div>
<p class="maodian"><a name="_label3_2_3_5"></a></p><h4>通过反射设置值</h4>
<div class="jb51code"><pre class="brush:go;">package main

import (
        "fmt"
        "reflect"
        "strings"
)

type User struct {
        Name1 string `big:"-"`
        Name2 string
}

func SetStruct(obj any) {
        v := reflect.ValueOf(obj).Elem()
        t := reflect.TypeOf(obj).Elem()
        for i := 0; i &lt; t.NumField(); i++ {
                field := t.Field(i)
                bigField := field.Tag.Get("big")
                // 判断类型是不是字符串
                if field.Type.Kind() != reflect.String {
                        continue
                }
                if bigField == "" {
                        continue
                }
                // 修改值
                valueFiled := v.Field(i)
                valueFiled.SetString(strings.ToTitle(valueFiled.String()))
        }
}

func main() {
        s := User{Name1: "name1", Name2: "name2"}
        SetStruct(&amp;s)
        fmt.Println(s)
}

</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>四:反射的优缺点</h2>
<p>优点:</p>
<ul><li>可以提升程序代码的灵活性,根据条件在程序运行时灵活的调用函数,并且修改源代码结构</li></ul>
<p>缺点:</p>
<ul><li>主要是性能影响,反射过程中会有大量的内存开辟和 gc 过程,导致程序的性能降低</li></ul>
<p class="maodian"><a name="_label4"></a></p><h2>五:共勉</h2>
頁: [1]
查看完整版本: Go语言反射使用及优缺点总结