斷言
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))}