断言
x.(T) 检查x的动态类型是否是T,其中x必须是接口值。
简单使用
func main() {
var x interface{}
x = 100
value1, 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 main
import (
"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]string
var json_map map[string]body_struct
json.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 = b
r.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 string
Age int
}
func main() {
a := 10
b := "s"
// 打印基本类型
reflectFn(a) // 类型是int,类型名称是int,种类是int
reflectFn(b) // 类型是string,类型名称是string,种类是string
// 打印自定义类型和结构体类型
var i myInt = 3
var p = Person{
"1",
2,
}
reflectFn(i) // 类型是main.myInt,类型名称是myInt,种类是int
reflectFn(p) // 类型是main.Person,类型名称是Person,种类是struct
// 打印指针类型
var f = 25
reflectFn(&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.Value
fmt.Printf("%v,%T", v.Int(), v.Int()) // 10,int64,获取到原始值和类型,因为传入是数字,所以是v.Int()
}
func main() {
var i = 10
reflectValue(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() // int64
if 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) // 字段名称:Age
fmt.Println(field1.Type) // 字段类型:int
fmt.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) // PrintInfo
fmt.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 string
Age 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 string
for 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))
}