各位學(xué)習(xí)Go語言的朋友兔毒,周末好咖楣,這次跟大家聊一聊Go語言的一個(gè)高級(jí)話題:反射。
這篇文章是從我過去的學(xué)習(xí)筆記修改來的处渣,內(nèi)容主要來自Go Blog的一篇文章《The law of reflection》伶贰。
這篇文章主要介紹反射和接口的關(guān)系,解釋內(nèi)在的關(guān)系和原理罐栈。
反射來自元編程黍衙,指通過類型檢查變量本身數(shù)據(jù)結(jié)構(gòu)的方式,只有部分編程語言支持反射荠诬。
類型
反射構(gòu)建在類型系統(tǒng)之上琅翻,Go是靜態(tài)類型語言,每一個(gè)變量都有靜態(tài)類型柑贞,在編譯時(shí)就確定下來了方椎。
比如:
type MyInt int
var i int
var j MyInt
i和j的底層類型都是int
,但i的靜態(tài)類型是int
钧嘶,j的靜態(tài)類型是MyInt
棠众,這兩個(gè)是不同類型,是不能直接賦值的有决,需要類型強(qiáng)制轉(zhuǎn)換闸拿。
接口類型比較特殊轿亮,接口類型的變量被多種對(duì)象類型賦值,看起來像動(dòng)態(tài)語言的特性胸墙,但變量類型始終是接口類型我注,Go是靜態(tài)的。舉例:
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
雖然r被3種類型的變量賦值迟隅,但r的類型始終是io.Reader
但骨。
最特別:空接口
interface{}
的變量可以被任何類型的值賦值,但類型一直都是interface{}
智袭。
接口的表示
Russ Cox(Go語言創(chuàng)始人)在他的博客詳細(xì)介紹了Go語言接口奔缠,結(jié)論是:
接口類型的變量存儲(chǔ)的是一對(duì)數(shù)據(jù):
- 變量實(shí)際的值
- 變量的靜態(tài)類型
例子:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
r是接口類型變量,保存了值tty和tty的類型*os.File
吼野,所以才能使用類型斷言判斷r保存的值的靜態(tài)類型:
var w io.Writer
w = r.(io.Writer)
雖然r中包含了tty和它的類型校哎,包含了tty的所有函數(shù),但r是接口類型瞳步,決定了r只能調(diào)用接口io.Reader
中包含的函數(shù)闷哆。
記住:接口變量保存的不是接口類型的值单起,還是英語說起來更方便:Interfaces do not hold interface values.
反射的3條定律
定律1:從接口值到反射對(duì)象
反射是一種檢測(cè)存儲(chǔ)在接口變量中值和類型的機(jī)制抱怔。通過reflect
包的一些函數(shù),可以把接口轉(zhuǎn)換為反射定義的對(duì)象嘀倒。
掌握reflect
包的以下函數(shù):
-
reflect.ValueOf({}interface) reflect.Value
:獲取某個(gè)變量的值屈留,但值是通過reflect.Value
對(duì)象描述的。 -
reflect.TypeOf({}interface) reflect.Type
:獲取某個(gè)變量的靜態(tài)類型测蘑,但值是通過reflect.Type
對(duì)象描述的灌危,是可以直接使用Println
打印的。 -
reflect.Value.Kind() Kind
:獲取變量值的底層類型(類別)碳胳,注意不是類型勇蝙,是Int、Float固逗,還是Struct浅蚪,還是Slice藕帜,具體見此烫罩。 -
reflect.Value.Type() reflect.Type
:獲取變量值的類型,效果等同于reflect.TypeOf
洽故。
再解釋下Kind和Type的區(qū)別贝攒,比如:
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
v.Kind()得到的是Int,而Type得到是MyInt
时甚。
定律2:從反射對(duì)象到接口值
定律2是定律1的逆向過程隘弊,上面我們學(xué)了:普通變量 -> 接口變量 -> 反射對(duì)象
的過程哈踱,這是從反射對(duì)象 -> 接口變量
的過程,使用的是Value
的Interface
函數(shù)梨熙,是把實(shí)際的值賦值給空接口變量开镣,它的聲明如下:
func (v Value) Interface() (i interface{})
回憶一下:接口變量存儲(chǔ)了實(shí)際的值和值的類型,Println
可以根據(jù)接口變量實(shí)際存儲(chǔ)的類型自動(dòng)識(shí)別其值并打印咽扇。
注意事項(xiàng):如果Value是結(jié)構(gòu)體的非導(dǎo)出字段邪财,調(diào)用該函數(shù)會(huì)導(dǎo)致panic。
定律3:當(dāng)反射對(duì)象所存的值是可設(shè)置時(shí)质欲,反射對(duì)象才可修改
從定律1入手理解树埠,定律3就不再那么難懂。
Settability is a property of a reflection Value, and not all reflection Values have it.
可設(shè)置指的是嘶伟,可以通過Value設(shè)置原始變量的值怎憋。
通過函數(shù)的例子思考一下可設(shè)置:
func f(x int)
在調(diào)用f的時(shí)候,傳入了參數(shù)x九昧,從函數(shù)內(nèi)部修改x的值绊袋,外部的變量的值并不會(huì)發(fā)生改變,因?yàn)檫@種是傳值铸鹰,是拷貝的傳遞方式愤炸。
func f(p *int)
函數(shù)f的入?yún)⑹侵羔橆愋停诤瘮?shù)內(nèi)部的修改變量的值掉奄,函數(shù)外部變量的值也會(huì)跟著變化规个。
使用反射也是這個(gè)原理,如果創(chuàng)建Value時(shí)傳遞的是變量姓建,則Value是不可設(shè)置的诞仓。如果創(chuàng)建Value時(shí)傳遞的是變量地址,則Value是可設(shè)置的速兔。
可以使用Value.CanSet()
檢測(cè)是否可以通過此Value修改原始變量的值墅拭。
x := 10
v1 := reflect.ValueOf(x)
fmt.Println("setable:", v1.CanSet())
p := reflect.ValueOf(&x)
fmt.Println("setable:", p.CanSet())
v2 := p.Elem()
fmt.Println("setable:", v2.CanSet())
如何通過Value設(shè)置原始對(duì)象值呢?
Value.SetXXX()
系列函數(shù)可設(shè)置Value中原始對(duì)象的值涣狗。
系列函數(shù)有:
- Value.SetInt()
- Value.SetUint()
- Value.SetBool()
- Value.SetBytes()
- Value.SetFloat()
- Value.SetString()
- ...
設(shè)置函數(shù)這么多谍婉,到底該選用哪個(gè)Set函數(shù)?
根據(jù)Value.Kind()
的結(jié)果去獲得變量的底層類型镀钓,然后選用該類別的Set函數(shù)穗熬。
參考資料
- 如果這篇文章對(duì)你有幫助,請(qǐng)點(diǎn)個(gè)贊/喜歡丁溅,感謝唤蔗。
- 本文作者:大彬
- 如果喜歡本文,隨意轉(zhuǎn)載,但請(qǐng)保留此原文鏈接:http://lessisbetter.site/2019/02/24/go-law-of-reflect/