《Go語言四十二章經(jīng)》第二十七章 反射(reflect)

《Go語言四十二章經(jīng)》第二十七章 反射(reflect)

作者:李驍

27.1 反射(reflect)

反射是應(yīng)用程序檢查其所擁有的結(jié)構(gòu)比吭,尤其是類型的一種能力睦擂;這是元編程的一種形式扭吁。每種語言的反射模型都不同,并且有些語言根本不支持反射啊胶。Go語言實(shí)現(xiàn)了反射肃拜,反射機(jī)制就是在運(yùn)行時(shí)動(dòng)態(tài)調(diào)用對(duì)象的方法和屬性,標(biāo)準(zhǔn)庫reflect提供了相關(guān)的功能真朗。在reflect包中此疹,通過reflect.TypeOf(),reflect.ValueOf()分別從類型遮婶、值的角度來描述一個(gè)Go對(duì)象蝗碎。

func TypeOf(i interface{}) Type
type Type interface 

func ValueOf(i interface{}) Value
type Value struct 

在Go語言的實(shí)現(xiàn)中,一個(gè)interface類型的變量存儲(chǔ)了2個(gè)信息, 一個(gè)<值蹭睡,類型>對(duì)衍菱,<value,type> :

(value, type)

value是實(shí)際變量值赶么,type是實(shí)際變量的類型肩豁。兩個(gè)簡(jiǎn)單的函數(shù),reflect.TypeOf 和 reflect.ValueOf辫呻,返回被檢查對(duì)象的類型和值清钥。

例如,x 被定義為:var x float64 = 3.4放闺,那么 reflect.TypeOf(x) 返回 float64祟昭,reflect.ValueOf(x) 返回 <float64 Value>。實(shí)際上怖侦,反射是通過檢查一個(gè)接口的值篡悟,變量首先被轉(zhuǎn)換成空接口。這從下面兩個(gè)函數(shù)簽名能夠很明顯的看出來:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

reflect.Type 和 reflect.Value 都有許多方法用于檢查和操作它們匾寝。

Type主要有:
Kind() 將返回一個(gè)常量搬葬,表示具體類型的底層類型
Elem()方法返回指針、數(shù)組艳悔、切片急凰、map、通道的基類型猜年,這個(gè)方法要慎用抡锈,如果用在其他類型上面會(huì)出現(xiàn)panic

Value主要有:
Type() 將返回具體類型所對(duì)應(yīng)的 reflect.Type(靜態(tài)類型)
Kind() 將返回一個(gè)常量,表示具體類型的底層類型

反射可以在運(yùn)行時(shí)檢查類型和變量乔外,例如它的大小床三、方法和 動(dòng)態(tài) 的調(diào)用這些方法。這對(duì)于沒有源代碼的包尤其有用杨幼。

由于反射是一個(gè)強(qiáng)大的工具勿璃,但反射對(duì)性能有一定的影響,除非有必要,否則應(yīng)當(dāng)避免使用或小心使用歧沪。下面代碼針對(duì)int、數(shù)組以及結(jié)構(gòu)體分別使用反射機(jī)制莲组,其中的差異請(qǐng)看注釋。

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    name string
}

func main() {

    var a int = 50
    v := reflect.ValueOf(a) // 返回Value類型對(duì)象锹杈,值為50
    t := reflect.TypeOf(a)  // 返回Type類型對(duì)象,值為int
    fmt.Println(v, t, v.Type(), t.Kind())

    var b [5]int = [5]int{5, 6, 7, 8}
    fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int

    var Pupil Student
    p := reflect.ValueOf(Pupil) // 使用ValueOf()獲取到結(jié)構(gòu)體的Value對(duì)象

    fmt.Println(p.Type()) // 輸出:Student
    fmt.Println(p.Kind()) // 輸出:struct

}

在Go語言中邪码,類型包括 static type和concrete type. 簡(jiǎn)單說 static type是你在編碼是看見的類型(如int、string)咬清,concrete type是實(shí)際的類型,runtime系統(tǒng)看見的類型旧烧。

Type()返回的是靜態(tài)類型,而kind()返回的是concrete type掘剪。上面代碼中平委,在int,數(shù)組以及結(jié)構(gòu)體三種類型情況中夺谁,可以看到kind()廉赔,type()返回值的差異。

通過反射可以修改原對(duì)象

d.CanAddr()方法:判斷它是否可被取地址
d.CanSet()方法:判斷它是否可被取地址并可被修改

通過一個(gè)settable的Value反射對(duì)象來訪問匾鸥、修改其對(duì)應(yīng)的變量值:

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    name string
    Age  int
}

func main() {

    var a int = 50
    v := reflect.ValueOf(a) // 返回Value類型對(duì)象蜡塌,值為50
    t := reflect.TypeOf(a)  // 返回Type類型對(duì)象,值為int
    fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem())
    seta := reflect.ValueOf(&a).Elem() // 這樣才能讓seta保存a的值
    fmt.Println(seta, seta.CanSet())
    seta.SetInt(1000)
    fmt.Println(seta)

    var b [5]int = [5]int{5, 6, 7, 8}
    fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem())

    var Pupil Student = Student{"joke", 18}
    p := reflect.ValueOf(Pupil) // 使用ValueOf()獲取到結(jié)構(gòu)體的Value對(duì)象

    fmt.Println(p.Type()) // 輸出:Student
    fmt.Println(p.Kind()) // 輸出:struct

    setStudent := reflect.ValueOf(&Pupil).Elem()
    //setStudent.Field(0).SetString("Mike") // 未導(dǎo)出字段扫腺,不能修改岗照,panic會(huì)發(fā)生
    setStudent.Field(1).SetInt(19)
    fmt.Println(setStudent)

}

雖然反射可以越過Go語言的導(dǎo)出規(guī)則的限制讀取結(jié)構(gòu)體中未導(dǎo)出的成員,但不能修改這些未導(dǎo)出的成員笆环。因?yàn)橐粋€(gè)struct中只有被導(dǎo)出的字段才是settable的攒至。

在結(jié)構(gòu)體中有tag標(biāo)簽,通過反射可獲取結(jié)構(gòu)體成員變量的tag信息躁劣。

package main

import (
    "fmt"
    "reflect"
)

type Student struct {
    name string
    Age  int `json:"years"`
}

func main() {
    var Pupil Student = Student{"joke", 18}
    setStudent := reflect.ValueOf(&Pupil).Elem()

    sSAge, _ := setStudent.Type().FieldByName("Age")
    fmt.Println(sSAge.Tag.Get("json")) // years
}

程序輸出:
years

27.2 反射結(jié)構(gòu)體

為了完整說明反射的情況迫吐,通過反射一個(gè)結(jié)構(gòu)體類型,綜合來說明账忘。下面例子較為系統(tǒng)地利用一個(gè)結(jié)構(gòu)體志膀,來充分舉例說明反射:

package main

import (
    "fmt"
    "reflect"
)

// 結(jié)構(gòu)體
type ss struct {
    int
    string
    bool
    float64
}

func (s ss) Method1(i int) string  { return "結(jié)構(gòu)體方法1" }
func (s *ss) Method2(i int) string { return "結(jié)構(gòu)體方法2" }

var (
    structValue = ss{ // 結(jié)構(gòu)體
        20, 
        "結(jié)構(gòu)體", 
        false, 
        64.0, 
    }
)

// 復(fù)雜類型
var complexTypes = []interface{}{
    structValue, &structValue, // 結(jié)構(gòu)體
    structValue.Method1, structValue.Method2, // 方法
}

func main() {
    // 測(cè)試復(fù)雜類型
    for i := 0; i < len(complexTypes); i++ {
        PrintInfo(complexTypes[i])
    }
}

func PrintInfo(i interface{}) {
    if i == nil {
        fmt.Println("--------------------")
        fmt.Printf("無效接口值:%v\n", i)
        fmt.Println("--------------------")
        return
    }
    v := reflect.ValueOf(i)
    PrintValue(v)
}

func PrintValue(v reflect.Value) {
    fmt.Println("--------------------")
    // ----- 通用方法 -----
    fmt.Println("String             :", v.String())  // 反射值的字符串形式
    fmt.Println("Type               :", v.Type())    // 反射值的類型
    fmt.Println("Kind               :", v.Kind())    // 反射值的類別
    fmt.Println("CanAddr            :", v.CanAddr()) // 是否可以獲取地址
    fmt.Println("CanSet             :", v.CanSet())  // 是否可以修改
    if v.CanAddr() {
        fmt.Println("Addr               :", v.Addr())       // 獲取地址
        fmt.Println("UnsafeAddr         :", v.UnsafeAddr()) // 獲取自由地址
    }
    // 獲取方法數(shù)量
    fmt.Println("NumMethod          :", v.NumMethod())
    if v.NumMethod() > 0 {
        // 遍歷方法
        i := 0
        for ; i < v.NumMethod()-1; i++ {
            fmt.Printf("    ┣ %v\n", v.Method(i).String())
            //          if i >= 4 { // 只列舉 5 個(gè)
            //              fmt.Println("    ┗ ...")
            //              break
            //          }
        }
        fmt.Printf("    ┗ %v\n", v.Method(i).String())
        // 通過名稱獲取方法
        fmt.Println("MethodByName       :", v.MethodByName("String").String())
    }

    switch v.Kind() {
    // 結(jié)構(gòu)體:
    case reflect.Struct:
        fmt.Println("=== 結(jié)構(gòu)體 ===")
        // 獲取字段個(gè)數(shù)
        fmt.Println("NumField           :", v.NumField())
        if v.NumField() > 0 {
            var i int
            // 遍歷結(jié)構(gòu)體字段
            for i = 0; i < v.NumField()-1; i++ {
                field := v.Field(i) // 獲取結(jié)構(gòu)體字段
                fmt.Printf("    ├ %-8v %v\n", field.Type(), field.String())
            }
            field := v.Field(i) // 獲取結(jié)構(gòu)體字段
            fmt.Printf("    └ %-8v %v\n", field.Type(), field.String())
            // 通過名稱查找字段
            if v := v.FieldByName("ptr"); v.IsValid() {
                fmt.Println("FieldByName(ptr)   :", v.Type().Name())
            }
            // 通過函數(shù)查找字段
            v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
            if v.IsValid() {
                fmt.Println("FieldByNameFunc    :", v.Type().Name())
            }
        }
    }
}
程序輸出:
String             : <main.ss Value>
Type               : main.ss
Kind               : struct
CanAddr            : false
CanSet             : false
NumMethod          : 1
    ┗ <func(int) string Value>
MethodByName       : <invalid Value>
=== 結(jié)構(gòu)體 ===
NumField           : 4
    ├ int      <int Value>
    ├ string   結(jié)構(gòu)體
    ├ bool     <bool Value>
    └ float64  <float64 Value>
--------------------
String             : <*main.ss Value>
Type               : *main.ss
Kind               : ptr
CanAddr            : false
CanSet             : false
NumMethod          : 2
    ┣ <func(int) string Value>
    ┗ <func(int) string Value>
MethodByName       : <invalid Value>
--------------------
String             : <func(int) string Value>
Type               : func(int) string
Kind               : func
CanAddr            : false
CanSet             : false
NumMethod          : 0
--------------------
String             : <func(int) string Value>
Type               : func(int) string
Kind               : func
CanAddr            : false
CanSet             : false
NumMethod          : 0

本書《Go語言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經(jīng)》內(nèi)容在簡(jiǎn)書同步地址: http://www.reibang.com/nb/29056963

雖然本書中例子都經(jīng)過實(shí)際運(yùn)行熙宇,但難免出現(xiàn)錯(cuò)誤和不足之處,煩請(qǐng)您指出溉浙;如有建議也歡迎交流烫止。
聯(lián)系郵箱:roteman@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市戳稽,隨后出現(xiàn)的幾起案子馆蠕,更是在濱河造成了極大的恐慌,老刑警劉巖惊奇,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件互躬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颂郎,警方通過查閱死者的電腦和手機(jī)吼渡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寺酪,“玉大人竭缝,你說我怎么就攤上這事沼瘫。” “怎么了湿故?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵坛猪,是天一觀的道長(zhǎng)皂股。 經(jīng)常有香客問我,道長(zhǎng)就斤,這世上最難降的妖魔是什么蘑辑? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任洋魂,我火速辦了婚禮喜鼓,結(jié)果婚禮上庄岖,老公的妹妹穿的比我還像新娘角骤。我一直安慰自己,他們只是感情好硼控,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布牢撼。 她就那樣靜靜地躺著疑苫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撼短。 梳的紋絲不亂的頭發(fā)上曲横,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天不瓶,我揣著相機(jī)與錄音,去河邊找鬼熙参。 笑死孽椰,一個(gè)胖子當(dāng)著我的面吹牛凛篙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鞋诗,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼全庸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了壶笼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤保礼,失蹤者是張志新(化名)和其女友劉穎炮障,沒想到半個(gè)月后胁赢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體白筹,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顽照。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纵穿,死狀恐怖奢人,靈堂內(nèi)的尸體忽然破棺而出何乎,到底是詐尸還是另有隱情,我是刑警寧澤支救,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布各墨,位于F島的核電站启涯,受9級(jí)特大地震影響恃轩,放射性物質(zhì)發(fā)生泄漏叉跛。R本人自食惡果不足惜蒸殿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酥艳。 院中可真熱鬧玖雁,春花似錦盖腕、人聲如沸赫冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雅任。三九已至,卻和暖如春硼婿,著一層夾襖步出監(jiān)牢的瞬間寇漫,已是汗流浹背殉摔。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工逸月, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓤湘。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惜索,于是被迫代替她去往敵國和親巾兆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虎囚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 第一次知道反射的時(shí)候還是許多年前在學(xué)校里玩 C# 的時(shí)候淘讥。那時(shí)總是弄不清楚這個(gè)復(fù)雜的玩意能有什么實(shí)際用途……然后發(fā)...
    勿以浮沙筑高臺(tái)閱讀 1,129評(píng)論 0 9
  • 首先巴拉巴拉一下golang反射機(jī)制的三個(gè)定律 1.反射可以從接口類型到反射類型對(duì)象 2.反射可以從反射類型對(duì)象到...
    吃貓的魚0閱讀 2,922評(píng)論 0 1
  • 先直觀感受下什么叫適配器 適配器模式有類的適配器模式和對(duì)象的適配器模式兩種不同的形式窒朋。 類適配器模式 對(duì)象適配器模...
    sunhq閱讀 166評(píng)論 0 0
  • 感恩(2017.12.26) 1.感恩同修成莉每日分享蓮花排毒操視頻侥猩,感恩分享感恩音頻抵赢!我堅(jiān)持練習(xí)蓮花排毒操第37...
    閃光的種子閱讀 231評(píng)論 0 0
  • 我想一個(gè)人站在陽臺(tái)下 看著遠(yuǎn)處陽光下的你 沒有憂愁 沒有煩惱 滿臉的開心 我想一個(gè)人獨(dú)自去旅行 走在無邊的草原聽...
    思雨曦月閱讀 243評(píng)論 3 7