首頁 > 軟體

Go語言學習之反射的用法詳解

2022-04-21 19:00:47

反射指的是執行時動態的獲取變數的相關資訊

1. reflect 包

型別是變數,類別是常數

reflect.TypeOf,獲取變數的型別,返回reflect.Type型別

reflect.ValueOf,獲取變數的值,返回reflect.Value型別

reflect.Value.Kind,獲取變數的類別,返回一個常數

reflect.Value.Interface(),轉換成interface{}型別

1.1 獲取變數型別

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	//反射資料型別
	t := reflect.TypeOf(i)
	fmt.Println("型別是", t)
	//反射資料值
	v := reflect.ValueOf(i)
	fmt.Println("值是", v)
}

func main() {
	a := "hello"
	Test(a)
}

輸出結果如下

型別是 string
值是 hello

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	//反射獲取型別
	t := reflect.TypeOf(i)
	fmt.Println("型別是", t)

	//反射獲取值
	v := reflect.ValueOf(i)
	//判斷值的類別
	c := v.Kind()
	fmt.Println("類別是", c)
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 80,
	}
	Test(stu)

	fmt.Println("-------------")
	var num int = 10
	Test(num)
}

輸出結果如下

型別是 main.Student
類別是 struct
-------------
型別是 int
類別是 int

1.2 斷言處理型別轉換

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	t := reflect.TypeOf(i)
	fmt.Println("型別是", t)

	//類別
	v := reflect.ValueOf(i)
	c := v.Kind()
	fmt.Println("類別是", c)
	fmt.Printf("c的型別是%Tn", c)
	fmt.Printf("v的型別是%Tn", v)

	//轉換成介面
	iv := v.Interface()
	fmt.Printf("iv的型別%Tn", iv)
	//斷言處理
	stu_iv, err := iv.(Student)
	if err {
		fmt.Printf("stu_iv的型別%Tn", stu_iv)
	}
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 80,
	}
	Test(stu)

}

輸出結果如下

型別是 main.Student
類別是 struct
c的型別是reflect.Kind
v的型別是reflect.Value
iv的型別main.Student
stu_iv的型別main.Student

2. ValueOf

2.1 獲取變數值

reflect.valueof(x).Float()

reflect.valueof(x).Int()

reflect.valueof(x).String()

reflect.Valueof(x).Bool()

2.2 型別轉換

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	fmt.Printf("v的型別是%Tn", v)
	//轉換成指定型別
	t := v.Int()
	fmt.Printf("t的型別是%Tn", t)
}

func main() {
	//型別不同的話會報錯
	var num int = 100
	Test(num)
}

輸出結果如下

v的型別是reflect.Value
t的型別是int64

3. Value.Set

3.1 設定變數值

reflect.Value.SetFloat(),設定浮點數

reflect.value.SetInt(),設定整數

reflect.Value.SetString(),設定字串

3.2 範例

package main

import (
	"fmt"
	"reflect"
)

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//更新值需要value的地址,否則會儲存,Elem()表示指標*
	v.Elem().SetInt(100)
	result := v.Elem().Int()
	fmt.Printf("result型別為 %T, 值為 %dn", result, result)
}

func main() {
	var num int = 10
	Test(&num)
}

輸出結果如下

result型別為 int64, 值為 100

4. 結構體反射

反射出結構體的屬性和方法數量

方法名需大寫,需要被跨包呼叫識別

4.1 檢視結構體欄位數量和方法數量

package main

import (
	"fmt"
	"reflect"
)

//結構體
type Student struct {
	Name  string
	Age   int
	Score float32
}

//結構體方法
func (s Student) Run() {
	fmt.Println("Running")
}

func (s Student) Sleep() {
	fmt.Println("Sleeping")
}

//使用反射檢視結構體欄位數量和方法數量
func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//類別判斷
	if v.Kind() != reflect.Struct {
		fmt.Println("不是結構體")
		return
	}
	//獲取結構體中欄位數量
	stu_num := v.NumField()
	fmt.Println("欄位數量: ", stu_num)
	//獲取結構體中方法數量
	stu_meth := v.NumMethod()
	fmt.Println("方法數量: ", stu_meth)
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 88,
	}
	Test(stu)
}

輸出結果如下

欄位數量:  3
方法數量:  2

4.2 獲取結構體屬性

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}) {
	v := reflect.ValueOf(i)
	//獲取結構體中每個屬性
	for i := 0; i < v.NumField(); i++ {
		//輸出屬性值
		fmt.Printf("%d %vn", i, v.Field(i))
		//輸出屬性值的型別
		fmt.Printf("%d %vn", i, v.Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 88,
	}
	Test(stu)
}

輸出結果如下

0 張三
0 string
1 18
1 int
2 88
2 float32

4.3 更改屬性值

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name  string
	Age   int
	Score float32
}

func Test(i interface{}, name string) {
	v := reflect.ValueOf(i)
	vk := v.Kind()
	//判斷是都為指標並指向結構體型別
	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//更改屬性值
	v.Elem().Field(0).SetString(name)
	//獲取結構體中每個屬性
	for i := 0; i < v.Elem().NumField(); i++ {
		//輸出屬性值
		fmt.Printf("%d %vn", i, v.Elem().Field(i))
		//輸出屬性值的型別
		fmt.Printf("%d %vn", i, v.Elem().Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 88,
	}
	Test(&stu, "李四")
}

輸出結果如下

0 李四
0 string
1 18
1 int
2 88
2 float32

4.4 Tag原資訊處理

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type Student struct {
	Name  string `json:"stu_name"`
	Age   int
	Score float32
}

func Test(i interface{}, name string) {
	v := reflect.ValueOf(i)
	vk := v.Kind()
	//判斷是都為指標並指向結構體型別
	if vk != reflect.Ptr && v.Elem().Kind() == reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//更改屬性值
	v.Elem().Field(0).SetString(name)
	//獲取結構體中每個屬性
	for i := 0; i < v.Elem().NumField(); i++ {
		//輸出屬性值
		fmt.Printf("%d %vn", i, v.Elem().Field(i))
		//輸出屬性值的型別
		fmt.Printf("%d %vn", i, v.Elem().Field(i).Kind())
	}
}

func main() {
	var stu Student = Student{
		Name:  "張三",
		Age:   18,
		Score: 88,
	}
	Test(&stu, "李四")
	fmt.Println("----------------json原資訊----------------")
	result, _ := json.Marshal(stu)
	fmt.Println("json原資訊: ", string(result))
	//反射獲取型別
	st := reflect.TypeOf(stu)
	s := st.Field(0)
	fmt.Printf("Name原資訊名稱: %sn", s.Tag.Get("json"))
}

輸出結果如下

0 李四
0 string
1 18
1 int
2 88
2 float32
----------------json原資訊----------------
json原資訊:  {"stu_name":"李四","Age":18,"Score":88}
Name原資訊名稱: stu_name

5. 函數反射

Go 中函數是可以賦值給變數的

範例:

既然函數可以像普通的型別變數一樣,那麼在反射機制中就和不同的變數是一樣的,在反射中函數和方法的型別(Type)都是reflect.Func,如果要呼叫函數,通過 Value 的Call() 方法

package main

import (
	"fmt"
	"reflect"
)

func hello() {
	fmt.Println("hello world")
}

func main() {
	//反射使用函數
	v := reflect.ValueOf(hello)
	//型別判斷是否屬於reflect.func型別
	if v.Kind() == reflect.Func {
		fmt.Println("函數")
	}
	//反射呼叫函數
	v.Call(nil)   //Call中需要傳入的是切片
}

輸出結果如下

函數
hello world

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射呼叫傳參和返回值函數
func Test(i int) string {
	return strconv.Itoa(i)
}

func main() {
	v := reflect.ValueOf(Test)
	//定義引數切片
	params := make([]reflect.Value, 1)
	//切片元素賦值
	params[0] = reflect.ValueOf(20)
	//反射調函數
	result := v.Call(params)
	fmt.Printf("result的型別是 %Tn", result)
	//[]reflect.Value切片轉換string
	s := result[0].Interface().(string)
	fmt.Printf("s的型別是 %T ,值為 %sn", s, s)
}

輸出結果如下

result的型別是 []reflect.Value
s的型別是 string ,值為 20

6. 方法反射

反射中方法的呼叫,函數和方法可以說其實本質上是相同的,只不過方法與一個“物件”進行了“繫結”,方法是“物件”的一種行為,這種行為是對於這個“物件”的一系列操作,例如修改“物件”的某個屬性

6.1 使用 MethodByName 名稱呼叫方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射方法
type Student struct {
	Name string
	Age  int
}

//結構體方法
func (s *Student) SetName(name string) {
	s.Name = name
}

func (s *Student) SetAge(age int) {
	s.Age = age
}

func (s *Student) String() string {
	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)
}

func main() {
	//範例化
	stu := &Student{"張三", 19}
	//反射獲取值:指標方式
	stuV := reflect.ValueOf(&stu).Elem()
	fmt.Println("修改前: ", stuV.MethodByName("String").Call(nil)[0])
	//修改值
	params := make([]reflect.Value, 1)		//定義切片
	params[0] = reflect.ValueOf("李四")
	stuV.MethodByName("SetName").Call(params)
	params[0] = reflect.ValueOf(20)
	stuV.MethodByName("SetAge").Call(params)
	fmt.Println("修改後: ", stuV.MethodByName("String").Call(nil)[0])
}

輸出結果如下

修改前:  0xc000004078,Name:張三,Age:19
修改後:  0xc000004078,Name:李四,Age:20

6.2 使用 method 索引呼叫方法

package main

import (
	"fmt"
	"reflect"
	"strconv"
)

//反射方法
type Student struct {
	Name string
	Age  int
}

//結構體方法
func (s *Student) B(name string) {
	s.Name = name
}

func (s *Student) A(age int) {
	s.Age = age
}

func (s *Student) C() string {
	return fmt.Sprintf("%p", s) + ",Name:" + s.Name + ",Age:" + strconv.Itoa(s.Age)
}

func main() {
	//範例化
	stu := &Student{"張三", 19}
	//反射獲取值:指標方式
	stuV := reflect.ValueOf(&stu).Elem()

	//索引呼叫方法
	fmt.Println("修改前: ", stuV.Method(2).Call(nil)[0])

	params := make([]reflect.Value, 1)
	params[0] = reflect.ValueOf("李四")
	stuV.Method(1).Call(params)

	params[0] = reflect.ValueOf(20)
	stuV.Method(0).Call(params)
	fmt.Println("修改後: ", stuV.Method(2).Call(nil)[0])
	//呼叫索引大小取決於方法名稱的ASCII大小進行排序
}

輸出結果如下

修改前:  0xc000004078,Name:張三,Age:19
修改後:  0xc000004078,Name:李四,Age:20

到此這篇關於Go語言學習之反射的用法詳解的文章就介紹到這了,更多相關Go語言反射內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com