import (
創(chuàng)新互聯(lián)建站是網(wǎng)站建設(shè)專家,致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營(yíng)銷,專業(yè)領(lǐng)域包括成都網(wǎng)站制作、做網(wǎng)站、電商網(wǎng)站制作開發(fā)、小程序開發(fā)、微信營(yíng)銷、系統(tǒng)平臺(tái)開發(fā),與其他網(wǎng)站設(shè)計(jì)及系統(tǒng)開發(fā)公司不同,我們的整合解決方案結(jié)合了恒基網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗(yàn)和互聯(lián)網(wǎng)整合營(yíng)銷的理念,并將策略和執(zhí)行緊密結(jié)合,且不斷評(píng)估并優(yōu)化我們的方案,為客戶提供全方位的互聯(lián)網(wǎng)品牌整合方案!
"fmt"
"reflect"
)
func reflecType(x interface{}){
v := reflect.TypeOf(x)
fmt.Println("type:%v\n", v)
fmt.Println("type name:%v , rtpe kind:%v \n", v.getName(), v.getType())
}
type Cat struct{}
//通過反射設(shè)置變量的值
func reflectSetValue1(x interface{}){
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64{
v.SetInt(200) //修改的是副本, reflect 包會(huì)引發(fā)panic
}
}
//通過反射設(shè)置變量的值
func reflectSetValue2(x interface{}){
v := reflect.ValueOf(x)
//反射中使用Elem()獲取指針對(duì)應(yīng)的值
if v.Elem().Kind() == reflect.Int64{
v.Elem().SetInt(200)
}
}
func main(){
var a float32 = 3.14
reflectType(a) //type name:float32 type kind:float32
var b int64 = 100
reflectType(b) // type name :int64 type kind :int64
var c = Cat{}
reflectType(c) // type name :Cat type kind :struct
reflectSetValue1(b)
fmt.Println(b) //依然為100
reflectSetValue2(b)
}
這是它的優(yōu)點(diǎn),因?yàn)榫幾g器在編譯時(shí)不去確定你傳的到底是什么類型,你傳一個(gè)string,它能接收,你傳一個(gè)對(duì)象struct,它也能接收,它只有一個(gè)要求,實(shí)現(xiàn)我要求實(shí)現(xiàn)的方法!
既然interface是不限定類型,是通用類型,這是一種開放表現(xiàn),這種開放怎么實(shí)現(xiàn)的呢?方法就是不去檢驗(yàn)?zāi)愕念愋?,既然不檢驗(yàn)?zāi)且膊蝗ビ涗浤愕念愋停。。?!注意interface不記錄你的類型,所以不管你是string,struct,int,我都不管,我都不記錄,我只記錄你的地址,結(jié)果是編譯器在編譯時(shí)也不知道你是什么類型,你有什么字段!
但是現(xiàn)在有一個(gè)問題,編譯器也沒辦法確定一個(gè)interface以前是什么類型?。ň幾g時(shí))這就是因果關(guān)系:為了達(dá)到通用,interface不做確定工作,結(jié)果就是interface也不知道以前的類型。
一個(gè)類型轉(zhuǎn)接口的過程,就是放棄自我類型的過程,變成了沒有類型。
這樣做有什么好處呢,很顯然是:通用,如果把一個(gè)函數(shù)的傳入?yún)?shù)設(shè)置為空接口(interface{}),那么任何類型當(dāng)做參數(shù)都能夠調(diào)用該接口,最好的例子就是:
它就是一個(gè)很標(biāo)準(zhǔn)的例子,println傳入?yún)?shù)可以是任何類型,都能打印出它的值。
當(dāng)然你可以說你記得,因?yàn)槭悄惆阉D(zhuǎn)換成interface,你理所當(dāng)然的記得,可編譯器不知道啊,interface不包含類型,也就是說你沒有讓它去記錄,所以它不知道。
針對(duì)這個(gè)問題,go語言給了一個(gè)解決方案,斷言,當(dāng)將一個(gè)interface轉(zhuǎn)換成它原來類型的時(shí)候,在它后面指明它的原來類型,這樣編譯器就知道該按照什么類型去解析了。(其實(shí)說白了,這就是通過人的記憶,編譯器不知道是什么類型,你告訴編譯器就可以了)
斷言其實(shí)是先獲取interface的動(dòng)態(tài)類型,然后與你指定的類型做判斷,如果一致,將它轉(zhuǎn)換成你指定的類型。如果不知道動(dòng)態(tài)類型,可以看這篇文章:
從報(bào)錯(cuò)可以看出, 不能直接轉(zhuǎn)換,需要對(duì)接口先進(jìn)行斷言
通常情況下,一個(gè)變量在確定類型的情況下編譯器知道他有哪些功能(注意,這里是針對(duì)編譯時(shí)),比如一個(gè)int類型,編譯器在編譯時(shí)知道能對(duì)他加減int,不能加減float,如果你這么做我就給你報(bào)錯(cuò)。一個(gè)struct包含哪些字段,不包含哪些字段,我定義一個(gè)user結(jié)構(gòu)體,里面只有name和age兩個(gè)字段,那么你只能取我這兩字段的值,你如果取height,我就給你報(bào)錯(cuò)。
這些都是正常情況下的,但是對(duì)于一個(gè)接口呢,編譯器會(huì)變成瞎子!在編譯的時(shí)候它不知道你原來是什么類型,所以它也沒法確定你包含什么字段,同樣是之前那個(gè)user結(jié)構(gòu)體,當(dāng)把它轉(zhuǎn)換成接口以后,編譯器就對(duì)它的類型一無所知了,你獲取name字段,這有接口有沒有呢?編譯器不知道!你請(qǐng)求height字段,這個(gè)泛型有沒有呢?編譯器仍然不知道。所以你編譯時(shí)不能修改接口里的數(shù)據(jù),既然編譯時(shí) 不能修改,那就只能在運(yùn)行時(shí)修改了。
這個(gè)時(shí)候就該反射登場(chǎng)了,它能夠在運(yùn)行時(shí)修改接口的數(shù)據(jù),通過追根溯源,獲取接口底層的實(shí)際數(shù)據(jù)和類型,讓你能夠?qū)涌诘脑磾?shù)據(jù)進(jìn)行操作。
換一種大白話的說法,反射就是刨根問底,獲取這個(gè)接口究竟是怎么產(chǎn)生的,因?yàn)槟呐乱粋€(gè)類型轉(zhuǎn)變成接口時(shí)放棄了自己的類型,但是它的本質(zhì)不會(huì)變的,就像趙本山的小品里所說:小樣,別以為你脫掉馬甲我就不認(rèn)識(shí)你了!對(duì),它的底層里仍然存儲(chǔ)了它的數(shù)據(jù)類型,只是藏的比較深,一般手段拿不到,但我們?nèi)匀荒軌蛲ㄟ^反射(這個(gè)包根問底的工具)來確定你究竟包含哪些字段和值,確定你究竟是蛇還是脫了馬甲的烏龜!
1、反射可以在運(yùn)行時(shí) 動(dòng)態(tài)獲取變量的各種信息 ,比如變量的類型、類別;
2、如果是結(jié)構(gòu)體變量,還可以獲取到結(jié)構(gòu)體本身的信息(包括結(jié)構(gòu)體的字段、方法);
3、通過反射,可以修改 變量的值 ,可以調(diào)用關(guān)聯(lián)的方法;
4、使用反射,需要import " reflect ".
5、示意圖:
1、不知道接口調(diào)用哪個(gè)函數(shù),根據(jù)傳入?yún)?shù)在運(yùn)行時(shí)確定調(diào)用的具體接口,這種需要對(duì)函數(shù)或方法反射。
例如以下這種橋接模式:
示例第一個(gè)參數(shù)funcPtr以接口的形式傳入函數(shù)指針,函數(shù)參數(shù)args以可變參數(shù)的形式傳入,bridge函數(shù)中可以用反射來動(dòng)態(tài)執(zhí)行funcPtr函數(shù)。
1、reflect.TypeOf(變量名),獲取變量的類型,返回reflect.Type類型。
2、reflect.ValueOf(變量名),獲取變量的值,返回reflect.Value類型reflect.Value是一個(gè)結(jié)構(gòu)體類型。
3、變量、interface{}和reflect.Value是可以互相轉(zhuǎn)換的,這點(diǎn)在實(shí)際開發(fā)中,會(huì)經(jīng)常使用到。
1、reflect.Value.Kind,獲取變量的 類別(Kind) ,返回的是一個(gè) 常量 。在go語言文檔中:
示例如下所示:
輸出如下:
Kind的范疇要比Type大。比如有Student和Consumer兩個(gè)結(jié)構(gòu)體,他們的 Type 分別是 Student 和 Consumer ,但是它們的 Kind 都是 struct 。
2、Type是類型,Kind是類別,Type和Kind可能是相同的,也可能是不同的。
3、通過反射可以在讓 變量 在 interface{} 和 Reflect.Value 之間相互轉(zhuǎn)換,這點(diǎn)在前面畫過示意圖。
4、使用反射的方式來獲取變量的值(并返回對(duì)應(yīng)的類型),要求數(shù)據(jù)類型匹配,比如x是int,那么久應(yīng)該使用reflect.Value(x).Int(),而不能使用其它的,否則報(bào)panic。
如果是x是float類型的話,也是要用reflect.Value(x).Float()。但是如果是struct類型的話,由于type并不確定,所以沒有相應(yīng)的方法,只能 斷言。
5、通過反射的來修改變量,注意當(dāng)使用SetXxx方法來設(shè)置需要通過對(duì)應(yīng)的指針類型來完成,這樣才能改變傳入的變量的值,同時(shí)需要使用到reflect.Value.Elem()方法。
輸出num=20,即成功使用反射來修改傳進(jìn)來變量的值。
6、reflect.Value.Elem()應(yīng)該如何理解?
在計(jì)算機(jī)科學(xué)領(lǐng)域,反射是指一類應(yīng)用,它們能夠自描述和自控制。也就是說,這類應(yīng)用通過采用某種機(jī)制來實(shí)現(xiàn)對(duì)自己行為的描述(self-representation)和監(jiān)測(cè)(examination),并能根據(jù)自身行為的狀態(tài)和結(jié)果,調(diào)整或修改應(yīng)用所描述行為的狀態(tài)和相關(guān)的語義。
每種語言的反射模型都不同,并且有些語言根本不支持反射。Golang語言實(shí)現(xiàn)了反射,反射機(jī)制就是在運(yùn)行時(shí)動(dòng)態(tài)的調(diào)用對(duì)象的方法和屬性,官方自帶的reflect包就是反射相關(guān)的,只要包含這個(gè)包就可以使用。
多插一句,Golang的gRPC也是通過反射實(shí)現(xiàn)的。
在講反射之前,先來看看Golang關(guān)于類型設(shè)計(jì)的一些原則
接下來要講的反射,就是建立在類型之上的,Golang的指定類型的變量的類型是靜態(tài)的(也就是指定int、string這些的變量,它的type是static type),在創(chuàng)建變量的時(shí)候就已經(jīng)確定,反射主要與Golang的interface類型相關(guān)(它的type是concrete type),只有interface類型才有反射一說。
在Golang的實(shí)現(xiàn)中,每個(gè)interface變量都有一個(gè)對(duì)應(yīng)pair,pair中記錄了實(shí)際變量的值和類型:
value是實(shí)際變量值,type是實(shí)際變量的類型。一個(gè)interface{}類型的變量包含了2個(gè)指針,一個(gè)指針指向值的類型【對(duì)應(yīng)concrete type】,另外一個(gè)指針指向?qū)嶋H的值【對(duì)應(yīng)value】。
例如,創(chuàng)建類型為*os.File的變量,然后將其賦給一個(gè)接口變量r:
接口變量r的pair中將記錄如下信息:(tty, *os.File),這個(gè)pair在接口變量的連續(xù)賦值過程中是不變的,將接口變量r賦給另一個(gè)接口變量w:
接口變量w的pair與r的pair相同,都是:(tty, *os.File),即使w是空接口類型,pair也是不變的。
interface及其pair的存在,是Golang中實(shí)現(xiàn)反射的前提,理解了pair,就更容易理解反射。反射就是用來檢測(cè)存儲(chǔ)在接口變量?jī)?nèi)部(值value;類型concrete type) pair對(duì)的一種機(jī)制。
既然反射就是用來檢測(cè)存儲(chǔ)在接口變量?jī)?nèi)部(值value;類型concrete type) pair對(duì)的一種機(jī)制。那么在Golang的reflect反射包中有什么樣的方式可以讓我們直接獲取到變量?jī)?nèi)部的信息呢? 它提供了兩種類型(或者說兩個(gè)方法)讓我們可以很容易的訪問接口變量?jī)?nèi)容,分別是reflect.ValueOf() 和 reflect.TypeOf(),看看官方的解釋
reflect.TypeOf()是獲取pair中的type,reflect.ValueOf()獲取pair中的value,示例如下:
當(dāng)執(zhí)行reflect.ValueOf(interface)之后,就得到了一個(gè)類型為”relfect.Value”變量,可以通過它本身的Interface()方法獲得接口變量的真實(shí)內(nèi)容,然后可以通過類型判斷進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換為原有真實(shí)類型。不過,我們可能是已知原有類型,也有可能是未知原有類型,因此,下面分兩種情況進(jìn)行說明。
已知類型后轉(zhuǎn)換為其對(duì)應(yīng)的類型的做法如下,直接通過Interface方法然后強(qiáng)制轉(zhuǎn)換,如下:
示例如下:
很多情況下,我們可能并不知道其具體類型,那么這個(gè)時(shí)候,該如何做呢?需要我們進(jìn)行遍歷探測(cè)其Filed來得知,示例如下:
通過運(yùn)行結(jié)果可以得知獲取未知類型的interface的具體變量及其類型的步驟為:
通過運(yùn)行結(jié)果可以得知獲取未知類型的interface的所屬方法(函數(shù))的步驟為:
reflect.Value是通過reflect.ValueOf(X)獲得的,只有當(dāng)X是指針的時(shí)候,才可以通過reflec.Value修改實(shí)際變量X的值,即:要修改反射類型的對(duì)象就一定要保證其值是“addressable”的。
示例如下:
這算是一個(gè)高級(jí)用法了,前面我們只說到對(duì)類型、變量的幾種反射的用法,包括如何獲取其值、其類型、如果重新設(shè)置新值。但是在工程應(yīng)用中,另外一個(gè)常用并且屬于高級(jí)的用法,就是通過reflect來進(jìn)行方法【函數(shù)】的調(diào)用。比如我們要做框架工程的時(shí)候,需要可以隨意擴(kuò)展方法,或者說用戶可以自定義方法,那么我們通過什么手段來擴(kuò)展讓用戶能夠自定義呢?關(guān)鍵點(diǎn)在于用戶的自定義方法是未可知的,因此我們可以通過reflect來搞定
示例如下:
Golang的反射很慢,這個(gè)和它的API設(shè)計(jì)有關(guān)。在 java 里面,我們一般使用反射都是這樣來弄的。
這個(gè)取得的反射對(duì)象類型是 java.lang.reflect.Field。它是可以復(fù)用的。只要傳入不同的obj,就可以取得這個(gè)obj上對(duì)應(yīng)的 field。
但是Golang的反射不是這樣設(shè)計(jì)的:
這里取出來的 field 對(duì)象是 reflect.StructField 類型,但是它沒有辦法用來取得對(duì)應(yīng)對(duì)象上的值。如果要取值,得用另外一套對(duì)object,而不是type的反射
這里取出來的 fieldValue 類型是 reflect.Value,它是一個(gè)具體的值,而不是一個(gè)可復(fù)用的反射對(duì)象了,每次反射都需要malloc這個(gè)reflect.Value結(jié)構(gòu)體,并且還涉及到GC。
Golang reflect慢主要有兩個(gè)原因
上述詳細(xì)說明了Golang的反射reflect的各種功能和用法,都附帶有相應(yīng)的示例,相信能夠在工程應(yīng)用中進(jìn)行相應(yīng)實(shí)踐,總結(jié)一下就是: