首頁  >  文章  >  後端開發  >  詳解Golang的反射(實例)

詳解Golang的反射(實例)

藏色散人
藏色散人轉載
2020-09-08 09:21:233795瀏覽

下面由golang教學欄位介紹給大家詳解Golang的反射(實例),希望對需要的朋友有幫助!

詳解Golang的反射(實例)

前言

反射在許多語言中都有其妙用。在電腦科學領域,反射是指一類應用,它們能夠***自描述***和***自控制***。

本文將記錄筆者Golang的反射的筆記。

10s後,以下知識點即將靠近:
1.反射的簡介
2.為什麼要使用反射?
3.反射具體能做什麼

正文

1.反射的簡介

#Golang提供了一種機制,在編譯時不知道類型的情況下,可更新變數、運行時查看值呼叫方法以及直接對他們的佈局進行操作的機制,稱為反射。

2.為什麼要使用反射?

打個比方,有時候我們需要一個函數可以處理各種類型的值。在不知道類型的情況下,你可能會這麼寫:

// 伪代码
switch value := value.(type) {
case string:
	// ...一些操作
case int:	
	// ...一些操作	
case cbsStruct: // 自定义的结构体	
	// ...一些操作

// ...
}

有沒發現什麼問題?
這邊存在一個問題:類型很多,這個函數會寫的非常長,而且還可能存在自定的類型,也就是說這個判斷日後可能還要一直改,因為無法知道未知值到底屬於什麼類型。

無法透視一個未知類型的時候,以上程式碼其實不是很合理,這時候就需要有反射來幫忙你處理,反射使用TypeOf和ValueOf函數從介面中取得目標物件的信息,輕鬆完成目的

3.反射具體能做什麼?

1.取得變數內部資訊

reflect提供了兩種類型來進行存取介面變數的內容:

類型 作用
reflect.ValueOf() 取得輸入參數介面中的資料的值,如果為空則回傳0 <- 注意是0
#reflect.TypeOf() 動態取得輸入參數介面中的值的類型,如果為空則回傳nil <- 注意是nil

上程式碼

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var name string = "咖啡色的羊驼"

	// TypeOf会返回目标数据的类型,比如int/float/struct/指针等
	reflectType := reflect.TypeOf(name)

	// valueOf返回目标数据的的值,比如上文的"咖啡色的羊驼"
	reflectValue := reflect.ValueOf(name)

	fmt.Println("type: ", reflectType)
	fmt.Println("value: ", reflectValue)
}

輸出:

type:  string
value:  咖啡色的羊驼

更深一層:在上述操作發生的時候,反射將“介面類型的變數”轉為了“反射的介面類型的變數”,例如上文實際上返回的是reflect.Value和reflect.Type的接口對象。 (可以根據ide追蹤相關函數回傳型別便知)

2.struct的反射

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Id   int
	Name string
}

func (s Student) Hello(){
	fmt.Println("我是一个学生")
}

func main() {
	s := Student{Id: 1, Name: "咖啡色的羊驼"}

	// 获取目标对象
	t := reflect.TypeOf(s)
	// .Name()可以获取去这个类型的名称
	fmt.Println("这个类型的名称是:", t.Name())

	// 获取目标对象的值类型
	v := reflect.ValueOf(s)
	// .NumField()来获取其包含的字段的总数
	for i := 0; i < t.NumField(); i++ {
		// 从0开始获取Student所包含的key
		key := t.Field(i)

		// 通过interface方法来获取key所对应的值
		value := v.Field(i).Interface()

		fmt.Printf("第%d个字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
	}

	// 通过.NumMethod()来获取Student里头的方法
	for i:=0;i<t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Printf("第%d个方法是:%s:%v\n", i+1, m.Name, m.Type)
	}
}

輸出:

这个类型的名称是: Student
第1个字段是:Id:int = 1 
第2个字段是:Name:string = 咖啡色的羊驼 
第1个方法是:Hello:func(main.Student)
##3 .匿名或嵌入欄位的反射

package main

import (
	"reflect"
	"fmt"
)

type Student struct {
	Id   int
	Name string
}

type People struct {
	Student // 匿名字段
}

func main() {
	p := People{Student{Id: 1, Name: "咖啡色的羊驼"}}

	t := reflect.TypeOf(p)
	// 这里需要加一个#号,可以把struct的详情都给打印出来
	// 会发现有Anonymous:true,说明是匿名字段
	fmt.Printf("%#v\n", t.Field(0))

	// 取出这个学生的名字的详情打印出来
	fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1}))

	// 获取匿名字段的值的详情
	v := reflect.ValueOf(p)
	fmt.Printf("%#v\n", v.Field(0))
}
輸出:

reflect.StructField{Name:"Student", PkgPath:"", Type:(*reflect.rtype)(0x10aade0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}

reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x109f4e0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}

main.Student{Id:1, Name:"咖啡色的羊驼"}
4.判斷傳入的型別是否是我們想要的型別

##

package main

import (
	"reflect"
	"fmt"
)

type Student struct {
	Id   int
	Name string
}

func main() {
	s := Student{Id: 1, Name: "咖啡色的羊驼"}
	t := reflect.TypeOf(s)

	// 通过.Kind()来判断对比的值是否是struct类型
	if k := t.Kind(); k == reflect.Struct {
		fmt.Println("bingo")
	}

	num := 1;
	numType := reflect.TypeOf(num)
	if k := numType.Kind(); k == reflect.Int {
		fmt.Println("bingo")
	}
}

輸出:
bingo
bingo

5.透過反射修改內容

package main

import (
	"reflect"
	"fmt"
)

type Student struct {
	Id   int
	Name string
}

func main() {
	s := &Student{Id: 1, Name: "咖啡色的羊驼"}

	v := reflect.ValueOf(s)

	// 修改值必须是指针类型否则不可行
	if v.Kind() != reflect.Ptr {
		fmt.Println("不是指针类型,没法进行修改操作")
		return
	}

	// 获取指针所指向的元素
	v = v.Elem()

	// 获取目标key的Value的封装
	name := v.FieldByName("Name")

	if name.Kind() == reflect.String {
		name.SetString("小学生")
	}

	fmt.Printf("%#v \n", *s)


	// 如果是整型的话
	test := 888
	testV := reflect.ValueOf(&test)
	testV.Elem().SetInt(666)
	fmt.Println(test)
}

輸出:
main.Student{Id:1, Name:"小学生"} 
666

6.透過反射呼叫方法

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Id   int
	Name string
}

func (s Student) EchoName(name string){
	fmt.Println("我的名字是:", name)
}

func main() {
	s := Student{Id: 1, Name: "咖啡色的羊驼"}

	v := reflect.ValueOf(s)

	// 获取方法控制权
	// 官方解释:返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装
	mv := v.MethodByName("EchoName")
	// 拼凑参数
	args := []reflect.Value{reflect.ValueOf("咖啡色的羊驼")}

	// 调用函数
	mv.Call(args)
}

輸出:
我的名字是: 咖啡色的羊驼

##4.反射的一些小點

1.使用反射時需要先確定要操作的值是否是期望的類型,是否是可以進行「賦值」操作的,否則reflect包將會毫不留情的產生一個panic。

2.反射主要與Golang的interface類型相關,只有interface型別才有反射一說。如果有興趣可以看看TypeOf和ValueOf,會發現其實傳入參數的時候已經被轉為介面類型了。

// 以下为截取的源代码
func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

func ValueOf(i interface{}) Value {
	if i == nil {
		return Value{}
	}
	escapes(i)

	return unpackEface(i)
}

#

以上是詳解Golang的反射(實例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除