Golang筆記—反射

反射(Reflection)

為什么需要反射

有時(shí)候需要知道未知類(lèi)型的類(lèi)型表達(dá)方式, 有時(shí)候需要獲取類(lèi)型信息, 進(jìn)行判斷進(jìn)行不同的處理

reflect.Typereflect.Value

reflect包中兩個(gè)重要的類(lèi)型.

  • reflect.Type是一個(gè)接口, 表示一個(gè)Go類(lèi)型
  • 可由reflect.TypeOf()reflect.Type的類(lèi)型返回某個(gè)interface{}的動(dòng)態(tài)類(lèi)型信息
t := reflect.TypeOf(3)  // t: a reflect.Type
fmt.Println(t.String()) // "int"
// reflect.Type滿(mǎn)足fmt.Stringer接口
fmt.Println(t)          // "int"

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File" not io.Writer
  • reflect.Value 可裝載任意類(lèi)型的值, 滿(mǎn)足Stringer接口, reflect.ValueOf()返回一個(gè)Value
v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v)          // "3"
fmt.Printf("%v\n", v)   // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"
  • ValueType方法返回reflect.Type
t := v.Type()           // a reflect.Type
fmt.Println(t.String()) // "int"
  • reflect.ValueOf() 的逆操作是 reflect.Value.Interface(): 返回一個(gè)interface{} 岳锁,其值是與Value相同的具體值
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface()      // an interface{}
i := x.(int)            // an int
fmt.Printf("%d\n", i)   // "3"
  • 跟interface{}不同的是無(wú)需用type斷言知道動(dòng)態(tài)類(lèi)型, 比如以下format.Any使用Kind方法,得到有限的Kind進(jìn)行處理
package format

import (
    "reflect"
    "strconv"
)

// Any formats any value as a string.
func Any(value interface{}) string {
    return formatAtom(reflect.ValueOf(value))
}

// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
    switch v.Kind() {
    case reflect.Invalid:
        return "invalid"
    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64:
        return strconv.FormatInt(v.Int(), 10)
    case reflect.Uint, reflect.Uint8, reflect.Uint16,
        reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return strconv.FormatUint(v.Uint(), 10)
    // ...floating-point and complex cases omitted for brevity...
    case reflect.Bool:
        return strconv.FormatBool(v.Bool())
    case reflect.String:
        return strconv.Quote(v.String())
    case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
        return v.Type().String() + " 0x" +
            strconv.FormatUint(uint64(v.Pointer()), 16)
    default: // reflect.Array, reflect.Struct, reflect.Interface
        return v.Type().String() + " value"
    }
}

display, 一個(gè)遞歸值打印器

聚合類(lèi)型只打印了類(lèi)型, 引用類(lèi)型打印地址, 需要進(jìn)一步精細(xì)化處理

Display("e", expr)

func Display(name string, x interface{}) {
    fmt.Printf("Display %s (%T):\n", name, x)
    display(name, reflect.ValueOf(x))
}

func display(path string, v reflect.Value) {
    switch v.Kind() {
    case reflect.Invalid:
        fmt.Printf("%s = invalid\n", path)
    case reflect.Slice, reflect.Array:
        for i := 0; i < v.Len(); i++ {
            display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
        }
    case reflect.Struct:
        for i := 0; i < v.NumField(); i++ {
            fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
            display(fieldPath, v.Field(i))
        }
    case reflect.Map:
        for _, key := range v.MapKeys() {
            display(fmt.Sprintf("%s[%s]", path,
                formatAtom(key)), v.MapIndex(key))
        }
    case reflect.Ptr:
        if v.IsNil() {
            fmt.Printf("%s = nil\n", path)
        } else {
            display(fmt.Sprintf("(*%s)", path), v.Elem())
        }
    case reflect.Interface:
        if v.IsNil() {
            fmt.Printf("%s = nil\n", path)
        } else {
            fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
            display(path+".value", v.Elem())
        }
    default: // basic types, channels, funcs
        fmt.Printf("%s = %s\n", path, formatAtom(v))
    }
}
  • SliceArray: Len()返回?cái)?shù)組元素個(gè)數(shù), Index()返回元素的reflect.Value,越界會(huì)panic
  • Struct: NumField返回結(jié)構(gòu)體字段個(gè)數(shù), Field(i)返回第i字段reflect.Value形式的值
  • Map: MapKeys返回一個(gè)reflect.Value類(lèi)型的slice, 對(duì)應(yīng)map的keys, 遍歷仍然是隨機(jī)的,MapIndex(key)返回key對(duì)應(yīng)的值Value
  • 指針:Elem返回指針指向的變量, 依然是reflect.Value類(lèi)型, nil也是安全的, type此時(shí)為Invalid類(lèi)型, 可用IsNil()事先判斷
  • 接口: 先IsNil判斷, 然后用v.Elem()獲取動(dòng)態(tài)值

通過(guò)reflect.Value修改值

有一些reflect.Values是可取地址的, 這種是可以設(shè)置其值的

x := 2                   // value   type    variable?
a := reflect.ValueOf(2)  // 2       int     no
b := reflect.ValueOf(x)  // 2       int     no
c := reflect.ValueOf(&x) // &x      *int    no
d := c.Elem()            // 2       int     yes (x)
fmt.Println(d.CanAddr()) // "true"

px := d.Addr().Interface().(*int) // px := &x
*px = 3                           // x = 3

d.Set(reflect.ValueOf(4))
  • CanAddr方法來(lái)判斷其是否可以被取地址
  • 通過(guò)調(diào)用可取地址的reflect.Value的reflect.Value.Set方法來(lái)更新
  • Set方法將在運(yùn)行時(shí)執(zhí)行和編譯時(shí)進(jìn)行類(lèi)似的可賦值性約束的檢查
  • Set方法:SetInt橡伞、SetUint、SetString和SetFloat等
  • 反射機(jī)制不能修改未導(dǎo)出成員
  • CanSet是用于檢查對(duì)應(yīng)的reflect.Value是否是可取地址并可被修改
  • reflect.Zero函數(shù)將變量v設(shè)置為零值
x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2)                     // OK, x = 2
rx.Set(reflect.ValueOf(3))       // OK, x = 3
rx.SetString("hello")            // panic: string is not assignable to int
rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int

var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2)                     // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3))       // OK, y = int(3)
ry.SetString("hello")            // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"

獲取結(jié)構(gòu)體字段標(biāo)簽

reflect.TypeField()將返回一個(gè)reflect.StructField, 含有每個(gè)成員的名字召嘶、類(lèi)型和可選的成員標(biāo)簽等信息蜻懦。其中成員標(biāo)簽信息對(duì)應(yīng)reflect.StructTag類(lèi)型的字符串间螟,并且提供了Get方法用于解析和根據(jù)特定key提取的子串

// Unpack populates the fields of the struct pointed to by ptr
// from the HTTP request parameters in req.
func Unpack(req *http.Request, ptr interface{}) error {
    if err := req.ParseForm(); err != nil {
        return err
    }

    // Build map of fields keyed by effective name.
    fields := make(map[string]reflect.Value)
    v := reflect.ValueOf(ptr).Elem() // the struct variable
    for i := 0; i < v.NumField(); i++ {
        fieldInfo := v.Type().Field(i) // a reflect.StructField
        tag := fieldInfo.Tag           // a reflect.StructTag
        name := tag.Get("http")
        if name == "" {
            name = strings.ToLower(fieldInfo.Name)
        }
        fields[name] = v.Field(i)
    }

    // Update struct field for each parameter in the request.
    for name, values := range req.Form {
        f := fields[name]
        if !f.IsValid() {
            continue // ignore unrecognized HTTP parameters
        }
        for _, value := range values {
            if f.Kind() == reflect.Slice {
                elem := reflect.New(f.Type().Elem()).Elem()
                if err := populate(elem, value); err != nil {
                    return fmt.Errorf("%s: %v", name, err)
                }
                f.Set(reflect.Append(f, elem))
            } else {
                if err := populate(f, value); err != nil {
                    return fmt.Errorf("%s: %v", name, err)
                }
            }
        }
    }
    return nil
}

func populate(v reflect.Value, value string) error {
    switch v.Kind() {
    case reflect.String:
        v.SetString(value)

    case reflect.Int:
        i, err := strconv.ParseInt(value, 10, 64)
        if err != nil {
            return err
        }
        v.SetInt(i)

    case reflect.Bool:
        b, err := strconv.ParseBool(value)
        if err != nil {
            return err
        }
        v.SetBool(b)

    default:
        return fmt.Errorf("unsupported kind %s", v.Type())
    }
    return nil
}

顯示一個(gè)類(lèi)型的方法集

  • reflect.Typereflect.Value都提供了Method方法
  • 每次t.Method(i)調(diào)用返回reflect.Method的實(shí)例
  • 每次v.Method(i)調(diào)用都返回一個(gè)reflect.Value以表示方法值
  • 使用reflect.Value.Call方法調(diào)用一個(gè)Func類(lèi)型的Value
// Print prints the method set of the value x.
func Print(x interface{}) {
    v := reflect.ValueOf(x)
    t := v.Type()
    fmt.Printf("type %s\n", t)

    for i := 0; i < v.NumMethod(); i++ {
        methType := v.Method(i).Type()
        fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
            strings.TrimPrefix(methType.String(), "func"))
    }
}

深度相等判斷

reflect.DeepEqual可以對(duì)兩個(gè)值進(jìn)行深度相等判斷, 使用基礎(chǔ)類(lèi)型的==判斷, 會(huì)遞歸復(fù)合類(lèi)型

func TestSplit(t *testing.T) {
    got := strings.Split("a:b:c", ":")
    want := []string{"a", "b", "c"};
    if !reflect.DeepEqual(got, want) { /* ... */ }
}

使用反射的忠告

  • 基于反射的代碼脆弱, 運(yùn)行時(shí)才會(huì)拋panic
  • 不能做靜態(tài)類(lèi)型檢查, 太多則可能難以理解
  • 運(yùn)行速度慢一到兩個(gè)數(shù)量級(jí), 測(cè)試適合使用, 性能關(guān)鍵路徑避免使用
  • 若非真正需要, 請(qǐng)不要使用reflect

Reference

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夸溶,一起剝皮案震驚了整個(gè)濱河市逸吵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缝裁,老刑警劉巖扫皱,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異捷绑,居然都是意外死亡韩脑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)粹污,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)段多,“玉大人,你說(shuō)我怎么就攤上這事壮吩●孟唬” “怎么了蕾总?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)琅捏。 經(jīng)常有香客問(wèn)我,道長(zhǎng)递雀,這世上最難降的妖魔是什么柄延? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮缀程,結(jié)果婚禮上搜吧,老公的妹妹穿的比我還像新娘。我一直安慰自己杨凑,他們只是感情好滤奈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著撩满,像睡著了一般蜒程。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伺帘,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天昭躺,我揣著相機(jī)與錄音,去河邊找鬼伪嫁。 笑死领炫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的张咳。 我是一名探鬼主播帝洪,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脚猾!你這毒婦竟也來(lái)了葱峡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤婚陪,失蹤者是張志新(化名)和其女友劉穎族沃,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泌参,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脆淹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沽一。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖溺。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铣缠,靈堂內(nèi)的尸體忽然破棺而出烘嘱,到底是詐尸還是另有隱情昆禽,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布蝇庭,位于F島的核電站醉鳖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哮内。R本人自食惡果不足惜盗棵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望北发。 院中可真熱鬧纹因,春花似錦、人聲如沸琳拨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狱庇。三九已至惊畏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僵井,已是汗流浹背陕截。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留批什,地道東北人农曲。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驻债,于是被迫代替她去往敵國(guó)和親乳规。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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

  • [TOC] Golang的反射reflect深入理解和示例 【記錄于2018年2月】 編程語(yǔ)言中反射的概念 在計(jì)算...
    AllenWu閱讀 862評(píng)論 1 14
  • 編程語(yǔ)言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域合呐,反射是指一類(lèi)應(yīng)用暮的,它們能夠自描述和自控制。也就是說(shuō)淌实,這類(lèi)應(yīng)用通過(guò)采用某種機(jī)...
    豆瓣奶茶閱讀 12,613評(píng)論 0 31
  • Golang的反射reflect深入理解和示例 編程語(yǔ)言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域冻辩,反射是指一類(lèi)應(yīng)用,它們能夠...
    陳臥蟲(chóng)閱讀 411評(píng)論 0 0
  • 序言 第一次接觸反射技術(shù)是在很多年前學(xué)習(xí)設(shè)計(jì)模式的時(shí)候拆祈,那時(shí)在優(yōu)化Java版簡(jiǎn)單工廠(chǎng)的實(shí)現(xiàn)恨闪,當(dāng)讀取配置信息中的的類(lèi)...
    _張曉龍_閱讀 4,629評(píng)論 2 21
  • 基礎(chǔ)類(lèi)型描述 Type Kindtype Kind uint 基礎(chǔ)類(lèi)型常量const ( Invalid K...
    copyLeft閱讀 1,688評(píng)論 0 0