Reflection 反射

出處: laws of reflection

反射在電腦方面代表的意思是程序認識自己的結構(structure)

而其中最重要最常使用的是型別(type)與 interface

下面是認識 reflection 需要的一些重點

type


type MyInt int

var i MyInt // i 目前為 0

n := 1 // 被默認宣告為 int

// i = n // cannot use n (type int) as type MyInt in assignment
// fmt.Println(i + n) // error (mismatched types MyInt and int)

fmt.Println(i + 1) // 1
fmt.Println(n + 1) // 2

example

interface

io.Reader 是一個 interface

var r io.Reader

// 以下型別都提供了 Read(p []byte) (int, error) 方法
// 所以都能賦值給 r
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)

下方程式碼重點在於說明一個變數在底層的儲存結構

valueName (value, type)

而當 empty 變數被宣告為 interface{} 後賦值時,編譯器將不會檢察 empty 的 type


// *os.File
f, err := os.OpenFile("some/file", os.O_RDWR, 0)
if err != nil {
    fmt.Println(err)
    return
}
defer f.Close()

var r io.Reader
// 將 f 指給 r 變數
r = f

var w io.Writer
// 將 f 指給 w 變數
w = f

// 無法將 r 指給 w
// w = r // cannot use r (type io.Reader) as type io.Writer ...

// 但因為 r 儲存的實際值是 *os.File 內部是有實作 Write(b []byte)(int, error)
// 所以可以以此方法將 r值 指給 w 變數
w, ok := r.(io.Writer)

fmt.Printf("%v, %T\n", ok, w) // true *os.File

var empty interface{}

empty = f
empty = r
empty = w

fmt.Printf("%T\n", empty) // *os.File

example

package reflect

反射有三個原則

1: 從 interface value 轉為 reflection object


var x float64 = 3.4

// reflect.Type
y := reflect.TypeOf(x)

fmt.Printf("%s \n%s \n%T \n%#v\n", y, y.Name(), y, y)

example

2: 從 reflection object 轉為 interface value


var x float64 = 3.4

// reflect.Value
y := reflect.ValueOf(x)

fmt.Println("toString:", y.String())
fmt.Println("type:", y.Type())
fmt.Println("kind is float64:", y.Kind() == reflect.Float64)
fmt.Println("value:", y.Float())

example


type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

fmt.Printf("%T\n", v) // reflect.Value
fmt.Printf("%T\n", v.Interface()) // main.MyInt

example

簡單來說, InterfaceValueOf 的反函數

3: reflection object 必須是可修改


var x float64 = 3.4
v := reflect.ValueOf(x)

// v.SetFloat(7.1) // panic: reflect: reflect.Value.SetFloat using unaddressable value

fmt.Println("settability of v:", v.CanSet())

example

阿不是說可以修改??

因為 reflect.ValueOf(x) 是將 x 值拷貝進 reflection object, 所以就算改動了物件內的值,也影響不到 x 變數的值

這樣的設計是為了可用性的考量

那如何取得一個可修改的 reflection object ?


var x float64 = 3.4

p := reflect.ValueOf(&x)

v := p.Elem()

fmt.Println(v.CanSet())

v.SetFloat(1.1)

fmt.Printf("%f", x) // 1.1

example

然而, 我們面臨到大部分狀況都會是需要查找 struct 內部屬性


    type MyStruct struct {
        A, B int
    }

    // 建立一個自訂結構,並將其指標存在 x 變數
    x := &MyStruct{1, 2}

    // 然後建立一個指標反射物件
    p := reflect.ValueOf(x)

    // 接著取得指標反射物件的 Value 物件
    v := p.Elem()

    // 再取得其 Type 物件
    t := v.Type()

    fmt.Printf("%#v\n", t.Name()) // 最後,我們找回最初的 type

    // 欄位屬性取得

    n := v.NumField()
    fmt.Println(n)

    for i := 0; i < n; i++ {
        f := v.Field(i)
        fmt.Printf("%d: %s %s = %v\n", 
            i, // index
            t.Field(i).Name, // Type 物件的欄位名稱
            f.Type(), // Value 物件 "第 i 個屬性" 的類型
            f.Interface()) // Value 物件 "第 i 個屬性" 的值
    }

    v.Field(0).SetInt(9)

    fmt.Printf("%#v\n", x) // &main.MyStruct{A:9, B:2}

example

在上面這個例子中 v 是"可修改的"反射物件, 就不再多說了

最後, 官網建議盡量減少或非必要時避免使用...