【go语言】reflect包与类型推断
reflect 包的核心概念
Go 中的反射涉及两个核心概念:
- Type:表示一个类型的结构体,
reflect.Type
是类型的描述。 - Value:表示一个值的结构体,
reflect.Value
是一个具体值的包装。
反射让我们能够动态地访问对象的类型和数据,并根据需要对其进行操作。
常用类型
reflect.Type
reflect.Type
是对 Go 类型的描述。可以通过它获取有关类型的信息,比如类型名、类型的种类、是否是指针、结构体的字段等。
常见方法:
t.Kind()
:获取reflect.Type
的底层类型(如int
、struct
、slice
等)。t.Name()
:获取类型的名称,仅对命名类型有效。t.NumField()
:获取结构体类型的字段数。t.Field(i)
:获取结构体的第i
个字段。
reflect.Value
reflect.Value
代表一个变量的值,它包含了具体的值,可以通过它获取或修改数据。
常见方法:
v.Kind()
:获取reflect.Value
的底层类型(如int
、struct
、slice
等)。v.Interface()
:将reflect.Value
转换为interface{}
类型。v.Set()
:修改reflect.Value
的值(需要是可修改的,即传入指针)。v.Type()
:获取reflect.Value
的类型。v.String()
:获取reflect.Value
的字符串表示。
常见的反射操作
获取类型和值
使用 reflect.TypeOf
获取类型,使用 reflect.ValueOf
获取值。
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
// 获取类型
t := reflect.TypeOf(x)
// 获取值
v := reflect.ValueOf(x)
fmt.Println("Type:", t) // 输出:Type: int
fmt.Println("Value:", v) // 输出:Value: 42
}
动态修改值
reflect
允许我们在运行时动态修改值。要修改值,必须传递指向变量的指针。
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
p := reflect.ValueOf(&x) // 传入指针
// 修改值
p.Elem().SetInt(100)
fmt.Println("Modified value:", x) // 输出:Modified value: 100
}
获取结构体字段
使用 reflect
获取结构体字段名和值。
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func printStructFields(s interface{}) {
val := reflect.ValueOf(s)
if val.Kind() == reflect.Struct {
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fmt.Printf("%s: %v\n", val.Type().Field(i).Name, field)
}
}
}
func main() {
p := Person{"Alice", 30}
printStructFields(p)
}
使用反射调用方法
反射不仅可以获取类型和值,还能动态调用方法。
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
}
func (p *Person) SayHello() {
fmt.Println("Hello, my name is", p.Name)
}
func main() {
p := &Person{Name: "Alice"}
// 获取反射对象
v := reflect.ValueOf(p)
// 获取方法并调用
method := v.MethodByName("SayHello")
method.Call(nil)
}
反射与类型断言的对比
类型断言与反射在用途上有很大区别:
- 类型断言:通常用于接口类型的断言,快速检查和转换接口类型为具体类型。
- reflect:允许动态地操作类型和值,可以用于获取更多类型信息或修改值。
示例:类型断言
package main
import "fmt"
func printType(i interface{}) {
if str, ok := i.(string); ok {
fmt.Println("String:", str)
} else if num, ok := i.(int); ok {
fmt.Println("Integer:", num)
} else {
fmt.Println("Unknown type")
}
}
func main() {
printType("Hello")
printType(42)
printType(3.14)
}
示例:使用 reflect 获取类型和值
package main
import (
"fmt"
"reflect"
)
func main() {
var x interface{} = 42
v := reflect.ValueOf(x)
t := reflect.TypeOf(x)
fmt.Println("Type:", t) // 输出:Type: int
fmt.Println("Value:", v) // 输出:Value: 42
}
总结:类型断言与反射对比
特性 | 类型断言 | reflect 包 |
---|---|---|
用途 | 用于接口类型的类型转换 | 用于动态类型检查、修改值、获取字段等 |
性能 | 高效,编译时确定类型 | 较慢,涉及运行时类型解析 |
语法简洁性 | 简单直观 | 语法较复杂 |
类型安全 | 类型安全,编译时检查 | 无类型安全,运行时可能出错 |
灵活性 | 灵活性较低,仅适用于接口类型断言 | 高度灵活,可动态修改、调用方法等 |
- 案例
package _case
import (
"fmt"
"reflect"
)
type student struct {
Name string `json:"name,omitempty" db:"name2"`
Age int `json:"age,omitempty"` // omitempty Zero-Value不序列化
}
type User struct {
Id int
Name string
Age int
}
// 匿名字段
type Boy struct {
User
Addr string
}
func (u User) Hello(name string) {
fmt.Println("hello", name)
}
func ReflectCase1() {
//reflectTest1()
//reflectType("cz")
//reflectValue(55.6)
//reflectTest2()
//u := User{1, "chen", 18}
//Poni(u)
//m := Boy{User{1, "sa", 20}, "bj"}
//reflectTest3(m)
//fmt.Println(u)
//setValue(&u)
//fmt.Println(u)
//userMethod(u)
//var s student
//getTag(&s)
}
func getTag(o any) {
v := reflect.ValueOf(o)
// 返回reflect.TypeOf类型
t := v.Type()
// 获取字段
for i := 0; i < t.Elem().NumField(); i++ {
f := t.Elem().Field(i)
fmt.Print(f.Tag.Get("json"), "\t")
fmt.Println(f.Tag.Get("db"))
}
}
func userMethod(o any) {
v := reflect.ValueOf(o)
// 获取方法
m := v.MethodByName("Hello")
// 有参数的话需要传一个Value类型切片
args := []reflect.Value{reflect.ValueOf("666")}
// 没有参数只需要:var args []reflect.Value
// m.Call()
m.Call(args)
}
func setValue(o any) {
v := reflect.ValueOf(o)
// 获取指针指向的元素
v = v.Elem()
// 取字段
f := v.FieldByName("Name")
if f.Kind() == reflect.String {
f.SetString("zhen")
}
}
func reflectTest3(o any) {
t := reflect.TypeOf(o)
fmt.Println(t)
// Anoymous:匿名
fmt.Printf("%#v\n", t.Field(0))
// 值信息
fmt.Printf("%#v\n", reflect.ValueOf(o).Field(0))
}
func Poni(o any) {
t := reflect.TypeOf(o)
fmt.Println("类型:", t)
fmt.Println("字符串类型:", t.Name())
// 获取值
v := reflect.ValueOf(o)
fmt.Println(v)
// 获取所有属性
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s : %v, ", f.Name, f.Type)
// 获取字段值信息
val := v.Field(i).Interface()
fmt.Println("val:", val)
}
fmt.Println("==method==")
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name)
fmt.Println(m.Type)
}
}
// 在处理处理少量已知类型时,使用类型断言+switch性能更好,reflect性能低
// 相较于使用interface{} + switch + 类型推断处理结构体时无法获取详细的字段或标签信息。
// reflect处理复杂结构体内的字段,具有优势可以获取结构体的字段、标签、方法等详细信息。
// reflect使用场景:处理大量动态、未知的复杂数据类型,且这些类型在编译时无法预知,使用 reflect 可以在运行时获取这些类型信息
// 实现通用代码
func reflectTest2() {
stu := student{
Name: "chenzhen",
Age: 19,
}
v := reflect.ValueOf(stu)
// 获取struct字段数量
fmt.Println("NumFields:", v.NumField())
// 获取字段Name值:
// 1.v.Field(指定字段序号) -> 适用于不知道字段名(或者结合for遍历操作)
// 2.v.FieldByName("指定字段名") -> 适用于知道字段名
fmt.Println("Name value:", v.Field(0).String(), ", ", v.FieldByName("Name").String())
// 字段类型
fmt.Println("Name type:", v.Field(0).Type())
t := reflect.TypeOf(stu)
for i := 0; i < t.NumField(); i++ {
// 获取字段名
name := t.Field(i).Name
fmt.Println("Field Name:", name)
// 获取tag
if fieldName, ok := t.FieldByName(name); ok {
tag := fieldName.Tag
fmt.Println("tag-", tag, ", ", "json:", tag.Get("json"), ", id", tag.Get("id"))
}
}
}
func reflectTest1() {
x := 1.2345
fmt.Println("TypeOf==")
// TypeOf()返回接口中保存值的类型
t := reflect.TypeOf(x)
fmt.Println("type:", t)
fmt.Println("kind:", t.Kind())
fmt.Println("ValueOf==")
v := reflect.ValueOf(x)
fmt.Println("value:", v)
fmt.Println("type:", v.Type())
fmt.Println("kind:", v.Kind())
// Float传入一个Value类型值,返回一个float64类型
fmt.Println("value:", v.Float())
z := v.Interface() // Interface()返回一个any类型值
fmt.Println(z)
fmt.Printf("value is %g\n", z)
x1 := []int{1, 2, 3}
v1 := reflect.ValueOf(x1)
fmt.Println("type:", v1.Type())
fmt.Println("kind:", v1.Kind())
x2 := map[string]string{"test1": "1", "test2": "2"}
v2 := reflect.ValueOf(x2)
fmt.Println("type:", v2.Type())
fmt.Println("kind:", v2.Kind())
fmt.Println("kind==")
// Kind()返回类型种类,与Type()区别为:如下案例,Kind返回更底层
type MyInt int
m := MyInt(5)
v3 := reflect.ValueOf(m)
fmt.Println("type:", v3.Type())
fmt.Println("kind:", v3.Kind())
}
func reflectType(a any) {
t := reflect.TypeOf(a)
fmt.Println("类型是:", t)
// kind()获取具体类型
k := t.Kind()
fmt.Println(k)
switch k {
case reflect.Float64:
fmt.Println("a is float64")
case reflect.String:
fmt.Println("string")
default:
panic("unhandled default case")
}
}
func reflectValue(a any) {
v := reflect.ValueOf(a)
fmt.Println(v)
fmt.Println(v.Type())
switch k := v.Kind(); k {
case reflect.Float64:
fmt.Println("a is ", v.Float())
default:
panic("unhandled default case")
}
}
package _case
import (
"errors"
"fmt"
"reflect"
)
func ReflectCase2() {
type user struct {
ID int64
Name string
Hobby []string
}
type outUser struct {
ID int64
Name string
Hobby []string
}
u := user{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}}
out := outUser{}
// 需求1:使用reflect动态copy struct
rs := copy(&out, u)
fmt.Println(rs, out)
// 需求2:
sliceUser := []user{
{ID: 1, Name: "nick", Hobby: []string{"篮球", "羽毛球"}},
{ID: 2, Name: "nick1", Hobby: []string{"篮球1", "羽毛球1"}},
{ID: 3, Name: "nick2", Hobby: []string{"篮球2", "羽毛球2"}},
}
slice := sliceColumn(sliceUser, "Hobby")
fmt.Println(slice)
}
// 从一个切片或结构体中提取指定字段(colu)的值,并返回一个包含这些值的切片
// 每次 t = t.Elem() 或 v = v.Elem() 都是为了处理某一层的指针解引用问题,以便获取实际的值或类型。
// 如果传入的切片类型涉及指针,例如 *[]*Struct,就需要多次解引用才能得到实际的元素类型和值。
// 对于四次t = t.Elem()解释
// reflect.Elem(),顾名思义,是取得变量的元素部分
// 在Golang中,变量的元素部分指的是指针指向的变量本身。
// 第一个 t = t.Elem() 处理传入 slice 是指针的情况。
// 第二个 t = t.Elem() 获取切片元素的类型。
// 第三个 t = t.Elem() 处理切片元素是指针的情况,获取指针指向的实际类型。
// o.Elem() 处理遍历时元素是指针的情况,解引用以访问字段。
// 我的理解:对于
//
// if t.Kind() == reflect.Ptr {
// t = t.Elem()
// v = v.Elem()
// }
// 第一个t = t.Elem()这是为了处理传入时传入的是切片地址的情况,如果传入的 slice 不是指针,比如 []Struct,这一段代码不会执行,因此不会影响后面的逻辑。
// 而如果传入的是切片,则会在第二个t = t.Elem()生效,这是因为切片打印出来是指向其第一个元素的地址,我们要的是其值,
// 所以要t = t.Elem()而接下来的
// if t.Kind() == reflect.Ptr {
// t = t.Elem()
// }则是为了应对其在切片内部还有一个切片指针的情况,需要获取其值而最后的:
// if o.Kind() == reflect.Ptr {
// v1 := o.Elem()
// val := v1.FieldByName(colu)
// s = reflect.Append(s, val)
// }则是处理切片中的切片中的field中指针的情况。
func sliceColumn(slice any, colu string) any {
t := reflect.TypeOf(slice)
v := reflect.ValueOf(slice)
// 因为这里传入一个切片,切片值为指向其第一个元素的地址,所以要elem
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
// 如果直接传入的slice是一个结构体,那么直接返回要找的colu对应值
if v.Kind() == reflect.Struct {
val := v.FieldByName(colu)
return val.Interface()
}
// 处理切片情况
if v.Kind() != reflect.Slice {
return nil
}
t = t.Elem()
// 如果还是一个指针,要找value,我们期望他是一个struct
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
f, _ := t.FieldByName(colu)
// 获取要找字段的类型
sliceT := reflect.SliceOf(f.Type)
// 根据类型创建切片
s := reflect.MakeSlice(sliceT, 0, 0)
for i := 0; i < v.Len(); i++ {
// index(i)返回v持有值的第i个元素。如果v的Kind不是Array、Chan、Slice、String,或者i出界,会panic
o := v.Index(i)
if o.Kind() == reflect.Struct {
val := o.FieldByName(colu)
s = reflect.Append(s, val)
}
if o.Kind() == reflect.Ptr {
v1 := o.Elem()
val := v1.FieldByName(colu)
s = reflect.Append(s, val)
}
}
return s.Interface()
}
func copy(dest any, source any) error {
// 对sorece的reflect处理
sT := reflect.TypeOf(source)
sV := reflect.ValueOf(source)
// 但是如果source传入的是指针,那么还要多操作一次,获取它的值
if sT.Kind() == reflect.Ptr {
sT = sT.Elem()
sV = sV.Elem()
}
// 对于dest的reflect处理
dT := reflect.TypeOf(dest)
dV := reflect.ValueOf(dest)
// 因为dest要被修改,所以传入的一定是指针
if dT.Kind() != reflect.Ptr {
return errors.New("target对象必须为指针类型")
}
dT = dT.Elem()
dV = dV.Elem()
// source必须为struct或者struct指针
if sV.Kind() != reflect.Struct {
return errors.New("sorce必须为struct或者struct指针")
}
// dest必须为struct指针
if dV.Kind() != reflect.Struct {
return errors.New("dest对象必须为struct指针")
}
// New()返回一个Value类型值,该值持有一个指向类型为传入类型的新申请的零值的指针,返回值的Type为PtrTo(typ)
// 这里destObj是待复制对象,所以new出zero-value
destObj := reflect.New(dT)
for i := 0; i < dT.NumField(); i++ {
// 每字段
dField := dT.Field(i)
if sField, ok := sT.FieldByName(dField.Name); ok {
if dField.Type != sField.Type {
continue
}
// 取sV中与dField.Name同名的Value赋给value
value := sV.FieldByName(dField.Name)
// 设置destObj(指针)对应dField.Name的字段的值为value
destObj.Elem().FieldByName(dField.Name).Set(value)
}
}
dV.Set(destObj.Elem())
// error nil
return nil
}