【go語言】reflect包與類型推斷

reflect 包的核心概念

Go 中的反射涉及兩個核心概念:

  • Type:表示一個類型的結構體,reflect.Type 是類型的描述。
  • Value:表示一個值的結構體,reflect.Value 是一個具體值的包裝。

反射讓我們能夠動態地訪問對象的類型和數據,并根據需要對其進行操作。

常用類型

reflect.Type

reflect.Type 是對 Go 類型的描述。可以通過它獲取有關類型的信息,比如類型名、類型的種類、是否是指針、結構體的字段等。

常見方法:

  • t.Kind():獲取 reflect.Type 的底層類型(如 intstructslice 等)。
  • t.Name():獲取類型的名稱,僅對命名類型有效。
  • t.NumField():獲取結構體類型的字段數。
  • t.Field(i):獲取結構體的第 i 個字段。

reflect.Value

reflect.Value 代表一個變量的值,它包含了具體的值,可以通過它獲取或修改數據。

常見方法:

  • v.Kind():獲取 reflect.Value 的底層類型(如 intstructslice 等)。
  • v.Interface():將 reflect.Value 轉換為 interface{} 類型。
  • v.Set():修改 reflect.Value 的值(需要是可修改的,即傳入指針)。
  • v.Type():獲取 reflect.Value 的類型。
  • v.String():獲取 reflect.Value 的字符串表示。

常見的反射操作

獲取類型和值

使用 reflect.TypeOf 獲取類型,使用 reflect.ValueOf 獲取值。

package mainimport ("fmt""reflect"
)func main() {var x int = 42// 獲取類型t := reflect.TypeOf(x)// 獲取值v := reflect.ValueOf(x)fmt.Println("Type:", t)     // 輸出:Type: intfmt.Println("Value:", v)    // 輸出:Value: 42
}

動態修改值

reflect 允許我們在運行時動態修改值。要修改值,必須傳遞指向變量的指針。

package mainimport ("fmt""reflect"
)func main() {var x int = 42p := reflect.ValueOf(&x) // 傳入指針// 修改值p.Elem().SetInt(100)fmt.Println("Modified value:", x) // 輸出:Modified value: 100
}

獲取結構體字段

使用 reflect 獲取結構體字段名和值。

package mainimport ("fmt""reflect"
)type Person struct {Name stringAge  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 mainimport ("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 mainimport "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 mainimport ("fmt""reflect"
)func main() {var x interface{} = 42v := reflect.ValueOf(x)t := reflect.TypeOf(x)fmt.Println("Type:", t) // 輸出:Type: intfmt.Println("Value:", v) // 輸出:Value: 42
}

總結:類型斷言與反射對比

特性類型斷言reflect 包
用途用于接口類型的類型轉換用于動態類型檢查、修改值、獲取字段等
性能高效,編譯時確定類型較慢,涉及運行時類型解析
語法簡潔性簡單直觀語法較復雜
類型安全類型安全,編譯時檢查無類型安全,運行時可能出錯
靈活性靈活性較低,僅適用于接口類型斷言高度靈活,可動態修改、調用方法等

  • 案例
package _caseimport ("fmt""reflect"
)type student struct {Name string `json:"name,omitempty" db:"name2"`Age  int    `json:"age,omitempty"` // omitempty Zero-Value不序列化
}type User struct {Id   intName stringAge  int
}// 匿名字段
type Boy struct {UserAddr 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).Namefmt.Println("Field Name:", name)// 獲取tagif fieldName, ok := t.FieldByName(name); ok {tag := fieldName.Tagfmt.Println("tag-", tag, ", ", "json:", tag.Get("json"), ", id", tag.Get("id"))}}
}func reflectTest1() {x := 1.2345fmt.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 intm := 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 _caseimport ("errors""fmt""reflect"
)func ReflectCase2() {type user struct {ID    int64Name  stringHobby []string}type outUser struct {ID    int64Name  stringHobby []string}u := user{ID: 1, Name: "nick", Hobby: []string{"籃球", "羽毛球"}}out := outUser{}// 需求1:使用reflect動態copy structrs := 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)// 因為這里傳入一個切片,切片值為指向其第一個元素的地址,所以要elemif 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,我們期望他是一個structif 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出界,會panico := 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-valuedestObj := 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賦給valuevalue := sV.FieldByName(dField.Name)// 設置destObj(指針)對應dField.Name的字段的值為valuedestObj.Elem().FieldByName(dField.Name).Set(value)}}dV.Set(destObj.Elem())// error nilreturn nil
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/63194.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63194.shtml
英文地址,請注明出處:http://en.pswp.cn/web/63194.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

跟著AI 學 AI, 開發一個ChatBot, 集成 Json 數據和查詢

按照規律&#xff0c;使用AI生成一個架構圖 直接上代碼&#xff0c;為了方便學習&#xff0c;直接按照如下方式&#xff0c;復制到你的開發環境即可調試&#xff0c;運行代碼。做學習參考。 代碼注釋多次說明這里&#xff0c;不在贅述。 "type": "carousel&qu…

WPF+MVVM案例實戰與特效(三十七)- 實現帶有水印和圓角的自定義 TextBox 控件

文章目錄 1、概述2、案例實現1、基本功能2、代碼實現3、控件應用4、案例效果4、總結1、概述 在開發用戶界面時,TextBox 是最常見的輸入控件之一。為了提升用戶體驗,我們經常需要為 TextBox 添加一些額外的功能,例如顯示提示文本(水印)和設置圓角邊框。本文將詳細介紹如何…

使用枚舉實現單例模式,不會反序列化破壞攻擊,不會被反射破壞攻擊。(附帶枚舉單例的簡單實現)

原因分析 1.反序列化方法 ① jdk8中的Enum源碼中對反序列化方法進行重寫&#xff0c;拋出異常。 java.lang.Enum#readObject方法截圖如下 ②java.io.ObjectInputStream#readObject 方法中的 readEnum 方法處理了枚舉類型的反序列化&#xff0c;從而確保了枚舉的單例特性。 …

Linux下的守護程序

啟動流程 嵌入式設備下Linux的內核系統啟動的流程并不復雜&#xff0c;從最早的父進程init開始&#xff0c;為創建各種服務進程&#xff1a;系統會從 inittab 文件中&#xff0c;讀取每一行作為執行命令&#x1f447; # Note: BusyBox init doesnt support runlevels. The r…

2024第十六屆藍橋杯模擬賽(第二期)-Python

# 2024第十六屆藍橋杯模擬賽&#xff08;第二期&#xff09;-Python題解 # 自己改注釋# -----------------------1------------------------ # def prime(x): # if x < 2: # return 0 # for i in range(2, int(x ** 0.5) 1): # if x % i 0: # …

MongoDB-副本集

一、什么是 MongoDB 副本集&#xff1f; 1.副本集的定義 MongoDB 的副本集&#xff08;Replica Set&#xff09;是一組 MongoDB 服務器實例&#xff0c;它們存儲同一數據集的副本&#xff0c;確保數據的高可用性和可靠性。副本集中的每個節點都有相同的數據副本&#xff0c;但…

《數據結構》(408代碼題)

2009 單鏈表&#xff08;雙指針&#xff09; 分析&#xff1a;首先呢&#xff0c;給我們的數據結構是一個帶有表頭結點的單鏈表&#xff0c;也不允許我們改變鏈表的結構。鏈表的長度不是直接給出的啊&#xff0c;所以這個倒數也很棘手。那我們該如何解決這個“k”呢&#xff0c…

6.1 初探MapReduce

MapReduce是一種分布式計算框架&#xff0c;用于處理大規模數據集。其核心思想是“分而治之”&#xff0c;通過Map階段將任務分解為多個簡單任務并行處理&#xff0c;然后在Reduce階段匯總結果。MapReduce編程模型包括Map和Reduce兩個階段&#xff0c;數據來源和結果存儲通常在…

Cad c#.net 一鍵修改標注dimension中的文本內容

本例為給標注加前綴&#xff0c;也可定制其他形式&#xff0c;效果如下&#xff1a; public class Demo{[CommandMethod("xx")]//public void Dim(){Document doc Application.DocumentManager.MdiActiveDocument;Database db doc.Database;Editor ed doc.Editor;…

Scala的隱式類

package hfd //隱式類 //任務&#xff1a;給之前的BaseUser添加新的功能&#xff0c;但是不要直接去改代碼 //思路&#xff1a;把BaseUser通過隱式轉換&#xff0c;改成一個新類型&#xff0c;而這個新類型中有這新的方法 //implicit class一個隱式轉換函數類 //作用&#xff1…

旅游系統旅游小程序PHP+Uniapp

旅游門票預訂系統&#xff0c;支持景點門票、導游產品便捷預訂、美食打卡、景點分享、旅游筆記分享等綜合系統 更新日志 V1.3.0 1、修復富文本標簽 2、新增景點入駐【高級版本】3、新增門票核銷【高級版】4、新增門票端口【高級版】

MacOS系統 快速安裝appium 步驟詳解

在macOS系統上&#xff0c;你可以通過使用nvm&#xff08;Node Version Manager&#xff09;來管理Node.js的版本&#xff0c;并基于nvm安裝的Node.js環境來快捷地安裝Appium。以下是具體步驟&#xff1a; 一、安裝nvm 下載nvm 訪問nvm的GitHub倉庫&#xff08;nvm GitHub&…

模型訓練中梯度累積步數(gradient_accumulation_steps)的作用

模型訓練中梯度累積步數&#xff08;gradient_accumulation_steps&#xff09;的作用 flyfish 在使用訓練大模型時&#xff0c;TrainingArguments有一個參數梯度累積步數&#xff08;gradient_accumulation_steps&#xff09; from transformers import TrainingArguments梯…

技術速遞|.NET 9 簡介

作者&#xff1a;.NET 團隊 排版&#xff1a;Alan Wang 今天&#xff0c;我們非常激動地宣布 .NET 9的發布&#xff0c;這是迄今為止最高效、最現代、最安全、最智能、性能最高的 .NET 版本。這是來自世界各地數千名開發人員又一年努力的成果。這個新版本包括數千項性能、安全和…

Vue項目打包部署到服務器

1. Vue項目打包部署到服務器 1.1. 配置 &#xff08;1&#xff09;修改package.json文件同級目錄下的vue.config.js文件。 // vue.config.js module.exports {publicPath: ./, }&#xff08;2&#xff09;檢查router下的index.js文件下配置的mode模式。 ??檢查如果模式改…

【jpa】springboot使用jpa示例

目錄 1. 請求示例2. pom依賴3. application.yaml4.controller5. service6. repository7. 實體8. 啟動類 1. 請求示例 curl --location --request POST http://127.0.0.1:8080/user \ --header User-Agent: Apifox/1.0.0 (https://apifox.com) \ --header Content-Type: applic…

uniapp 常用的指令語句

uniapp 是一個使用 Vue.js 開發的跨平臺應用框架&#xff0c;因此&#xff0c;它繼承了 Vue.js 的大部分指令。以下是一些在 uniapp 中常用的 Vue 指令語句及其用途&#xff1a; v-if / v-else-if / v-else 條件渲染。v-if 有條件地渲染元素&#xff0c;v-else-if 和 v-else 用…

中企出海-德國會計準則和IFRS間的差異

根據提供的網頁內容&#xff0c;德國的公認會計準則&#xff08;HGB&#xff09;與國際會計準則&#xff08;IFRS&#xff09;之間的主要差異可以從以下幾個方面進行比較&#xff1a; 財務報告的目的&#xff1a; IFRS&#xff1a;財務報告主要是供投資者做決策使用&#xff0c…

NPU是什么?電腦NPU和CPU、GPU區別介紹

隨著人工智能技術的飛速發展&#xff0c;計算機硬件架構也在不斷演進以適應日益復雜的AI應用場景。其中&#xff0c;NPU&#xff08;Neural Processing Unit&#xff0c;神經網絡處理器&#xff09;作為一種專為深度學習和神經網絡運算設計的新型處理器&#xff0c;正逐漸嶄露頭…

使用skywalking,grafana實現從請求跟蹤、 指標收集和日志記錄的完整信息記錄

Skywalking是由國內開源愛好者吳晟開源并提交到Apache孵化器的開源項目&#xff0c; 2017年12月SkyWalking成為Apache國內首個個人孵化項目&#xff0c; 2019年4月17日SkyWalking從Apache基金會的孵化器畢業成為頂級項目&#xff0c; 目前SkyWalking支持Java、 .Net、 Node.js、…