GoLang 反射

📢 本文由 gemini-3-flash-preview 翻譯

Golang Series

Hello GoLang: https://blog.yexca.net/archives/154
GoLang (var and const) 變數與常數: https://blog.yexca.net/archives/155
GoLang (func) 函式: https://blog.yexca.net/archives/156
GoLang (slice and map) 切片: https://blog.yexca.net/archives/160
GoLang (OOP) 物件導向: https://blog.yexca.net/archives/162
GoLang (reflect) 反射:本文
GoLang (struct tag) 結構體標籤: https://blog.yexca.net/archives/205
GoLang (goroutine) Goroutine: https://blog.yexca.net/archives/206
GoLang (channel) 通道: https://blog.yexca.net/archives/207


反射指一類應用,它們能夠自我描述與自我控制

pair

Go 語言變數包括 type (型別) 和 value (值) 部分,組成 pair

image

static type 是在編碼時開發者看見的型別,concrete type 是在 runtime 系統看見的型別

型別斷言能否成功,取決於變數的 concrete type,而不是 static type。因此一個 read 變數如果 concrete type 也實作了 write 方法的話,也可以被型別斷言為 write

反射建立於型別之上,靜態型別已經固定,因此反射主要與 interface 型別相關 (它是 concrete type)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

type ReadBook interface {
    Read()
}
type WriteBook interface {
    Write()
}

type Book struct {
}

func (this *Book) Read() {
    fmt.Println("read Book")
}
func (this *Book) Write() {
    fmt.Println("write Book")
}

func main() {
    // b: pair<type:Book, value:book{}位址>
    b := &Book{}

    // r: pair<type:, value:>
    var r ReadBook
    // r: pair<type:Book, value:book{}位址>
    r = b
    r.Read()

    var w WriteBook
    w = r.(WriteBook) // 因為 r 的 type 是 Book,所以可行
    w.Write()
}

TypeOf 與 ValueOf

reflect.TypeOf() 是獲取 pair 中的 type,reflect.ValueOf() 獲取 pair 中的 value

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a)) // float64
    fmt.Println("value of a:", reflect.ValueOf(a)) // 3.14
}

類型轉換

執行 reflect.ValueOf() 後得到型別為 reflect.Value 的變數

已知原始資料型別

已知原始資料型別可以直接強制轉換

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a))
    fmt.Println("value of a:", reflect.ValueOf(a))

    value := reflect.ValueOf(a)
    fmt.Printf("type of value:%T\n", value) // reflect.Value
    newA := value.Interface().(float64)
    fmt.Printf("type of newA:%T\n", newA) // float64
}

需要注意型別轉換需要完全一致,否則將會 panic,例如指標

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a))
    fmt.Println("value of a:", reflect.ValueOf(a))

    // 傳遞位址
    point := reflect.ValueOf(&a)
    fmt.Printf("type of value:%T\n", point) // reflect.Value
    // 轉換型別為指標
    newA := point.Interface().(*float64)
    fmt.Printf("type of newA:%T\n", newA) // *float64
}

也就是說反射可以將 “反射型別物件” 再重新轉換為 “介面型別變數”

未知原始資料型別

透過遍歷探尋 Field 獲得

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    // 一行為一個 field
    Name string
    Age  int
    Rank float64
}

// 注意方法為公有,若私有則無法存取
func (person Person) Sleep() {
    fmt.Println("person sleep")
}

func main() {
    p := Person{"zhangSan", 18, 5.2}
    getFieldAndMethod(p)
}

func getFieldAndMethod(input interface{}) {
    getType := reflect.TypeOf(input)
    fmt.Println("type:", getType.Name())

    getValue := reflect.ValueOf(input)
    fmt.Println("value/AllField:", getValue)

    // 獲取屬性
    numField := getValue.NumField()
    for i := 0; i < numField; i++ {
        fieldType := getType.Field(i)
        fieldValue := getValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", fieldType.Name, fieldType.Type, fieldValue)
    }

    // 獲取方法
    numMethod := getType.NumMethod()
    for i := 0; i < numMethod; i++ {
        method := getType.Method(i)
        fmt.Printf("%s: %v\n", method.Name, method.Type)
    }
}

透過 reflect.Value 賦值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14

    // 只有是指標時才可賦值
    pointer := reflect.ValueOf(&a)
    // 設置指向該位址,獲取原始值
    newValue := pointer.Elem()
    
    // 判斷是否可以設置值
    fmt.Println("value canSet:", newValue.CanSet())
    if newValue.CanSet() {
        // 如果可以設置
        newValue.SetFloat(9.96)
        // 顯示變數值
        fmt.Println("value of a:", a) // 9.96
    } else {
        fmt.Println("error")
    }
}

透過 reflect.Value 呼叫方法

透過函式名稱呼叫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    name string
    age  int
}

func (user User) MethodHasArgs(a string, b int) {
    fmt.Println("User MethodHasArgs")
}
func (user User) MethonNotArgs() {
    fmt.Println("User MethodNotArgs")
}

func main() {
    user := User{"zhangSan", 18}
    value := reflect.ValueOf(user)

    // 透過函式名稱呼叫
    method1 := value.MethodByName("MethodHasArgs")
    // 構建參數
    args1 := []reflect.Value{reflect.ValueOf("string"), reflect.ValueOf(18)}
    // 呼叫函式
    method1.Call(args1)

    // 無參呼叫
    method2 := value.MethodByName("MethonNotArgs")
    args2 := make([]reflect.Value, 0)
    method2.Call(args2)

}

反射的基本原理

image