断言
x.(T) 检查x的动态类型是否是T,其中x必须是接口值。
简单使用
func main() {var x interface{}x = 100value1, ok := x.(int)if ok {fmt.Println(value1)}value2, ok := x.(string)if ok {//未打印fmt.Println(value2)}
}
需要注意如果不接受第二个参数就是OK,这里失败的话则会直接panic,这里还存在一种情况就是x为nil同样会panic。
func main() {var x interface{}x = "Hello World"value := x.(int)fmt.Println(value)
}
panic: interface conversion: interface {} is string, not int
所以就有这种写法
switch写法
func JudgeType(a interface{}) {switch value := a.(type) {case nil:fmt.Println("Type is nil")case int:fmt.Println("int", value)case string:fmt.Println("string:", value)case A:fmt.Println("类型A:", value)case *A:fmt.Println("指针A", value)default:fmt.Println("未匹配")}
}
注意 断言好像是只能一层一层去处理的 不能一次直接
package mainimport ("encoding/json""fmt"
)func main() {json_str := `{"font_size": 0.4,"lang": "zh","version": "en-v1.2.0.4-t0.3.c","body": [{"content": "最近很多人问我这个概念"},{"content": "在过去的一年里我一直很喜欢"}]}`var json_map map[string]interface{}json.Unmarshal([]byte(json_str), &json_map)//这里直接一步到位 断言成[]map[string]interface {} 会报错 panic: interface conversion: interface {} is []interface {}, not []map[string]interface {}//同理[]map[string]string也不可以body := json_map["body"].([]interface{})for _, item := range body {itemMap := item.(map[string]interface{}) //map[string]string依然报错fmt.Println(itemMap["content"])}}
在这个例子中就是 我们每次只能断言一个级别 比如第一层断言是个切片[],第二层断言是个map[string]
以及注意 interface类型 都要加{}
但是 在处理json的时候
type body_struct []map[string]stringvar json_map map[string]body_structjson.Unmarshal([]byte(json_str), &json_map)
可以直接这样,猜测是json这个包内部进行了处理吧
反射
前置知识
用到了前面断言的知识
Go语言中的变量是分为两部分的:
• 类型信息:预先定义好的元信息。
• 值信息:程序运行过程中可动态变化的。
在 GoLang 的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。
golang变量包含两个属性 type和value,这是一个pair对。
type Reader interface {ReadBook( )
}
type Writer interface {WriteBook()
}
//一个具体的结构体
type Book struct {
}
//Book类实现了Reader接口
func(this *Book)ReadBook(){fmt.Println("Read a Book")
}
Book类实现了Writer 接口
func(this *Book)WriteBook(){fmt.Println("Write a Book")
}
func main(){//b:pair<type:Book,value:book{}地址>b := &Book{}//r: pair<type:, value:>var r Reader//r:pair<type:Book, value:book{}地址>r = br.ReadBook()var w writer//r: pair<type:Book,value:book{}地址>w=r.(Writer)//此处的断言为什么会成功?因为w与r 具体的type是一致
简单的理解就是 Book实现了两个接口,断言成功就是reader指向的book对象可以转为writer
是什么
反射是静态语言具有动态属性的体现
反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们
有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射
反射的功能
-
反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
-
如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag。
-
通过反射,可以修改变量的值,可以调用关联的方法
reflect包与基本用法
在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并 且 reflect 包 提 供 了
- reflect.TypeOf
- reflect.ValueOf
两个重要函数来获取任意对象的 Value 和 Type
%T不也可以获取类型么?----可能%T就是通过反射来得到的
reflect.TypeOf()
使用 reflect.TypeOf()函数可以接受任意 interface{}参数,可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)
因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,
当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。
Go 语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空
import ("fmt""reflect"
)func reflectFn(x interface{}) {// 反射获取任意变量类型v := reflect.TypeOf(x)name := v.Name() // 类型名称kind := v.Kind() // 种类(底层类型)fmt.Printf("类型是%v,类型名称是%v,种类是%v \n", v, name, kind)}// 自定义一个myInt类型
type myInt int// Person结构体
type Person struct {Name stringAge int
}func main() {a := 10b := "s"// 打印基本类型reflectFn(a) // 类型是int,类型名称是int,种类是intreflectFn(b) // 类型是string,类型名称是string,种类是string// 打印自定义类型和结构体类型var i myInt = 3var p = Person{"1",2,}reflectFn(i) // 类型是main.myInt,类型名称是myInt,种类是intreflectFn(p) // 类型是main.Person,类型名称是Person,种类是struct// 打印指针类型var f = 25reflectFn(&f) // 类型是*int,类型名称是,种类是ptr}
与前面x.(type)区别
a.(type)
是类型断言中的特殊语法,仅在 switch 语句中使用,用于通过接口变量获取其动态类型。例如:
switch v := a.(type) {
case int:fmt.Println("int:", v)
case string:fmt.Println("string:", v)
}
该语法直接通过接口值的动态类型进行分支判断46。
reflect.TypeOf(a)
是反射包中的函数,返回 reflect.Type 接口类型的对象,用于获取任意值的类型信息。例如:
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())
该函数通过反射机制解析值的类型,适用于动态类型检查12。
功能与灵活性
.(type)
仅能判断接口变量的动态类型,无法获取更详细的类型元信息(如结构体字段、方法等)。
需在代码中显式列出所有可能的类型分支,适用于类型已知或有限的场景
`reflect.TypeOf``返回完整的类型元信息,包括类型名称(Name)、种类(Kind)、结构体字段、方法等。
支持动态处理任意类型,适用于编写通用代码(如序列化、ORM 框架)或需要深度类型分析的场景
性能开销
a.(type)
类型断言是 Go 语言的内置语法,性能开销极低,几乎等同于普通的条件判断。
reflect.TypeOf
反射操作需要运行时动态解析类型信息,涉及额外的内存分配和间接调用,性能开销较高。应避免在性能敏感的场景中使用
reflect.ValueOf() 获取原始值
reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换
func reflectValue(x interface{}) {// 通过反射获取到值v := reflect.ValueOf(x)fmt.Printf("%v, %T \n", v, v) // 10, reflect.Value,获取到的类型是reflect.Valuefmt.Printf("%v,%T", v.Int(), v.Int()) // 10,int64,获取到原始值和类型,因为传入是数字,所以是v.Int()
}
func main() {var i = 10reflectValue(i)
func reflectValue(x interface{}) {v := reflect.ValueOf(x)kind := v.Kind() // 获取种类// 判断底层种类 switch kind {// 是int类型case reflect.Int64:fmt.Println(v.Int())case reflect.Float64:fmt.Println(v.Float())case reflect.String:fmt.Println(v.String())}
}
set通过反射设置变量的值
import ("fmt""reflect"
)func reflectValue(x interface{}) {v := reflect.ValueOf(x)// 如果传入的值是一个指针地址,那需要通过Elem().Kind()获取具体种类,Elem()返回 Type 对象所表示的指针指向的数据// 如果只是v.kind,获取的到的是指针类型kind := v.Elem().Kind() // int64if kind == reflect.Int64 {// int是SetInt string是SetString等v.Elem().SetInt(3)}
}func main() {var i = 10// 值类型修改副本不会影响原始值,所以需要通过指针地址reflectValue(&i)fmt.Println(i) // 3}
import (
“fmt”
“reflect”
)
// studen结构体
type Student struct {
Name string json:"name"
Age int json:"age"
Score int json:"score"
}
// 结构体方法 获取学生信息
func (s Student) GetInfo() string {
return fmt.Sprintf(“姓名%v 年龄%v 成绩%v”, s.Name, s.Age, s.Score)
}
// 结构体方法 修改学生信息
func (s *Student) SetInfo(name string, age, score int) {
s.Name = name
s.Age = age
s.Score = score
}
// 结构体方法 打印
func (s Student) PrintInfo() {
fmt.Println(“print info”)
}
func PrintStructField(s interface{}) {
// 获取类型对象
t := reflect.TypeOf(s)
/*
通过类型变量里面的Field获取结构体字段*/
// 通过类型变量.Field(下标)可以获取结构体的字段对象
field0 := t.Field(0)// 获取到结构体的Name字段对象
fmt.Println(field0) // {Name string json:"name" 0 [0] false}fmt.Printf("%#v \n", field0) // reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0xe748c80), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
// 获取字段名称
fmt.Println(field0.Name) // Name
// 获取字段类型
fmt.Println(field0.Type) // string
// 获取字段tag - json类型
fmt.Println(field0.Tag.Get("json")) // name/*通过类型变量里面的FieldByName获取结构体字段
*/
// FieldByName返回两个值,一个是具体的值,一个是是否成功
field1, ok := t.FieldByName("Age")
if ok {fmt.Println(field1.Name) // 字段名称:Agefmt.Println(field1.Type) // 字段类型:intfmt.Println(field1.Tag.Get("json")) // 字段的json类型的tag :age
}/*通过类型变量的NumField获取该结构体有多少个字段
*/var count = t.NumField()
fmt.Println(count) // 3
}
func main() {
var student = Student{"小李", 15, 100}
PrintStructField(student)
}
通过值变量获取结构体值的值信息#
func PrintStructField(s interface{}) {
/*
通过值变量获取结构体属性对应的值
*/
v := reflect.ValueOf(s)
// 方式一
fmt.Println(v.FieldByName(“Name”)) // 小李
fmt.Println(v.FieldByName(“Age”)) // 15
// 方式二 可以通过循环获取所有的属性值
fmt.Println(v.Field(2)) // 100
}
通过反射修改结构体的属性值#
func ReflectSetValue(s interface{}) {
// 类型对象
t := reflect.TypeOf(s)
// 值对象
v := reflect.ValueOf(s)
// 判断传入的结构体是否是指针类型,因为非指针是值类型,不能修改值,必现使用指针类型
if t.Kind() != reflect.Ptr {
fmt.Println(“传入的不是指针类型”)
return
// 判断传入的指针是不是结构体指针
} else if t.Elem().Kind() != reflect.Struct {fmt.Println("传入的不是结构体指针")return
}
// 获取Name字段的指针值
name := v.Elem().FieldByName("Name")
// 修改属性值
name.SetString("小王")
}
func main() {
var student = Student{"小李", 15, 100}
// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
ReflectSetValue(&student)
fmt.Println(student.Name) // 小王
}
通过类型变量获取结构体方法#
// 打印结构体方法
func PrintStructFn(s interface{}) {
t := reflect.TypeOf(s)
// 通过类型变量的Method(下标)获取结构体的方法
method0 := t.Method(0) // 下标取的方法和结构体的顺序没有关系,和结构体的ASCII码有关系
fmt.Println(method0.Name) // 下标为0的方法名:GetInfo
fmt.Println(method0.Type) // func(main.Student) string// 通过MethodByName 获取结构体方法
// 该方法两个返回值,一个是方法名,一个是是否有该方法的状态
method1, ok := t.MethodByName("PrintInfo")
if ok {fmt.Println(method1.Name) // PrintInfofmt.Println(method1.Type) // func(main.Student)}
// 获取结构体一共有几个方法
fmt.Println(t.NumMethod()) // 2 Student结构体定义了3个方法,有两个接收者类型是结构体,有一个接收者类型是指针接收者,如果s接收的是值接收者,只能统计值接收者的方法数量,如果是指针接收者,可以统计所有的方法的数量
}
func main() {
var student = Student{"小李", 15, 100}
PrintStructFn(student)
}
通过值变量执行结构体方法#
func RunStructFn(s interface{}) {
v := reflect.ValueOf(s)
// 方法一 通过下标执行对应的方法,Call传递对应参数,nil代表没有参数
v.Method(1).Call(nil) // print info
// 方法二 通过MethodByName执行对应方法,返回值是一个切片
// 传入参数调用,Call方法传入的参数需要是一个reflect.Value类型的切片
var params []reflect.Value
params = append(params, reflect.ValueOf("小李"))
params = append(params, reflect.ValueOf(20))
params = append(params, reflect.ValueOf(95))
v.MethodByName("SetInfo").Call(params)fmt.Println(v.MethodByName("GetInfo").Call(nil)) // [姓名小李 年龄20 成绩95]
}
func main() {
var student = Student{"小李", 15, 100}
// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
RunStructFn(&student)
}
应用场景
(1)动态类型检查
反射可以用于在运行时检查变量的类型,这在处理未知类型的数据时非常有用。例如,编写通用函数或库时,可能需要根据传入参数的类型执行不同的操作。
func checkType(data interface{}) {t := reflect.TypeOf(data)fmt.Println("Type:", t)
}
(2)动态调用方法
反射可以用于在运行时动态调用对象的方法,这在需要根据条件调用不同方法时非常有用
type MyStruct struct{}func (m *MyStruct) MyMethod() {fmt.Println("MyMethod called")
}func callMethod(obj interface{}, methodName string) {v := reflect.ValueOf(obj)method := v.MethodByName(methodName)if method.IsValid() {method.Call(nil)}
}
(3)结构体字段操作比如处理配置文件
反射可以用于在运行时动态访问和修改结构体的字段,这在处理配置文件、数据库映射等场景时非常有用。
type Person struct {Name stringAge int
}func setField(obj interface{}, fieldName string, value interface{}) {v := reflect.ValueOf(obj).Elem()field := v.FieldByName(fieldName)if field.IsValid() && field.CanSet() {field.Set(reflect.ValueOf(value))}
}
动态绑定配置字段
通过反射动态解析配置文件(如 YAML、JSON),将键值对自动映射到预定义结构体的字段中,无需手动逐字段赋值。
典型实现:
读取配置文件内容后,遍历结构体的反射元数据,匹配键名与字段名(支持大小写转换、json/yaml 标签解析)。
处理嵌套结构体时,递归遍历子结构体字段,实现复杂配置的自动加载8。
支持热加载配置
因为已经变成二进制文件了 配置新消息变成了一个内存中的结构体 所以我们要用反射才能找到它?或者 因为外部是不确定的 你不知道传进来的配置文件是啥样的
通过反射动态更新配置对象,实现运行时重载配置文件(如 SIGHUP 信号触发)而不重启服务。
实现逻辑:
- 监听配置文件变化,重新读取文件内容并生成新配置对象。
- 通过反射对比新旧配置对象差异,仅更新发生变化的字段值(避免全局替换导致状态不一致)
统一配置处理接口
编写通用配置解析函数,支持多种格式(JSON/YAML/TOML)的自动适配。
代码示例:
func LoadConfig(filePath string, config interface{}) error {data, _ := os.ReadFile(filePath) // 读取文件val := reflect.ValueOf(config).Elem()// 根据文件后缀选择解析逻辑(如解析为 map[string]interface{})parsedData := parseByFileType(filePath, data) // 反射遍历结构体字段并赋值for key, value := range parsedData {field := val.FieldByName(strings.Title(key)) if field.IsValid() && field.CanSet() {field.Set(reflect.ValueOf(value).Convert(field.Type())) }}return nil
}
// 调用示例:LoadConfig("config.yaml", &ServerConfig{})
说明:通过反射的 FieldByName 和 Set 方法实现键值动态映射,支持类型转换(如字符串转 int)。
处理环境变量与默认值
结合反射实现配置优先级逻辑(如环境变量 > 配置文件 > 默认值)。
实现方式:
-遍历结构体字段,检查是否存在 env 标签(如 env:“PORT”),优先从环境变量读取值。
若未配置环境变量或文件字段,通过反射检查并设置字段的默认值(如 default:“8080” 标签)。
动态校验配置合法性
利用反射实现字段类型校验或自定义规则检查(如端口号范围、必填字段)。
示例逻辑:
func ValidateConfig(config interface{}) error {t := reflect.TypeOf(config).Elem()v := reflect.ValueOf(config).Elem()for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i)// 检查必填标签(如 `required:"true"`)if field.Tag.Get("required") == "true" && value.IsZero() {return fmt.Errorf("字段 %s 必填", field.Name)}}return nil
}
用途:防止因配置缺失或格式错误导致服务启动失败58。
注意事项
性能开销:反射操作比静态代码慢,建议在初始化阶段或低频热加载场景使用,避免高频调用
(4)序列化与反序列化
反射可以用于实现通用的序列化和反序列化功能,例如将结构体转换为JSON或从JSON解析为结构体
func toJSON(obj interface{}) string {v := reflect.ValueOf(obj)t := reflect.TypeOf(obj)var result stringfor i := 0; i < v.NumField(); i++ {field := t.Field(i)value := v.Field(i)result += fmt.Sprintf("%s: %v\n", field.Name, value.Interface())}return result
}
(5)插件系统
反射可以用于实现插件系统,动态加载和调用插件中的函数或方法。
func loadPlugin(pluginPath string, functionName string) {p, err := plugin.Open(pluginPath)if err != nil {log.Fatal(err)}f, err := p.Lookup(functionName)if err != nil {log.Fatal(err)}f.(func())()
}
(6)依赖注入
反射可以用于实现依赖注入框架,动态地将依赖注入到对象中。
type Service struct {Dependency *Dependency
}func injectDependency(service interface{}, dependency interface{}) {v := reflect.ValueOf(service).Elem()field := v.FieldByName("Dependency")if field.IsValid() && field.CanSet() {field.Set(reflect.ValueOf(dependency))}