Go

Go_Reflection

Reflection 反射

反射指的是程式”運行”期間動態的調用對象的方法和屬性.
Golang內建這功能, 在”reflect”包裡.

思考一下interface{}這類型, 什麼都能代入.
但這麼方便的話, 我們幹麻不全傳它就好XD
Go在這裡面做了很多反射取型別的動作判斷.
不然幹麻出type assertion(類型斷言)這類的東西.
就是要想辦法告訴Go這變數是什麼類型, 要怎使用它.

Type & Value & Kind

一個變數一定包含 type和value兩個部份.
理解這點就知道為什麼 nil != nil, 像是字串的nil跟int的nil就不會一樣.

Kind

對象隸屬的種類…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const (
Invalid Kind = iota
Bool
Int
...
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)

很多…基本上就粗分成
基礎型別, 引用型別, 指針這樣

Type

Type代表的是對象的類別資訊

  • TypeOf 透過TypeOf取得對象的Type資訊

    1
    func TypeOf(i interface{}) Type
  • Elem 透過Elem取得對象包含的值或者是對象是個指針所指向的值

    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
    package main

    import (
    "fmt"
    "reflect"
    )

    type Enum int

    const (
    Zero Enum = 0
    )

    func main() {
    type cat struct {
    }

    // 實例化物件
    typeOfCat := reflect.TypeOf(cat{})
    fmt.Println(typeOfCat.Name(), typeOfCat.Kind())

    // 基礎型別
    typeOfA := reflect.TypeOf(Zero)
    fmt.Println(typeOfA.Name(), typeOfA.Kind())

    // 指針類型, 指針是沒有類別名稱的, 不是nil是空字串
    catInstance := &cat{}
    typeOfCatInstance := reflect.TypeOf(catInstance)
    fmt.Printf("name: '%v' kind:'%v'\n", typeOfCatInstance.Name(), typeOfCatInstance.Kind())

    // 透過Elem()來取得這指針指向的對象
    typeOfCatInstanceElem := typeOfCatInstance.Elem()
    fmt.Printf("name: '%v' kind:'%v'\n", typeOfCatInstanceElem.Name(), typeOfCatInstanceElem.Kind())
    }

    /*
    cat struct
    Enum int
    name: '' kind:'ptr'
    name: 'cat' kind:'struct'
    */

Value

Value就對象的原始值

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

import (
"fmt"
"reflect"
)

func main() {
var a int = 1024
// 透過valueof 取得對象的值
valueOfA := reflect.ValueOf(a)

// 將對象的值以interface{} 類型返回取得
// 再做類型斷言
var getA int = valueOfA.Interface().(int)

// 將對象的值以int 類型返回取得
var getA2 int = int(valueOfA.Int())

fmt.Println(getA, getA2)
}
// 1024 1024

獲得struct的成員訊息

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package main

import (
"fmt"
"reflect"
)

// struct
type Dummy struct {
a int
b string
float32
bool
// 指針成員
next *Dummy
nilPtr *Dummy
}

func main() {
// dummy實例
dummyIns := Dummy{
next: &Dummy{},
}
// 取得成員屬性數量
d := reflect.ValueOf(dummyIns)
fmt.Println("NumField:", d.NumField())

// 透過索引來取得成員屬性
floatField := d.Field(2)
fmt.Println("Field2 :", floatField.Type())

// 透過屬性名稱來取得成員屬性
fmt.Println("FiledByName:(\"b\".Type", d.FieldByName("b").Type())

// 根據[]int的的值當索引取得成員屬性數量
// []int{4, 0}, 4先取得的是next; 0指向的就是最初的成員 a int
fmt.Println("FieldByIndex([]ubt{4,0}).Type()", d.FieldByIndex([]int{4, 0}).Type())

// IsNil()判斷該成員是不是nil
fmt.Println("nilPtr *Dummy IsNill()", d.FieldByName("nilPtr").IsNil())

// IsValid()判斷該成員的值是否合法, 就是看是否有值, 或是值是否有效
// nilPtr 是個指針本身有值, 只是指向nil
fmt.Println("nilPtr *Dummy IsValid()", d.FieldByName("nilPtr").IsValid())
// 查找abc()
fmt.Println("func abc()", d.MethodByName("abc").IsValid())

// 這裡故意存取一個不存在的成員
fmt.Println("notExist property IsValid()", d.FieldByName("").IsValid())
// nil
fmt.Println("nil IsValid()", reflect.ValueOf(nil).IsValid())

// 從map中查找一個不存在的鍵
mapIns := make(map[string]int)
fmt.Println("not exist key of map IsValid()", reflect.ValueOf(mapIns).MapIndex(reflect.ValueOf("aa")).IsValid())
}

/*
NumField: 6
Field2 : float32
FiledByName:("b".Type string
FieldByIndex([]ubt{4,0}).Type() int
nilPtr *Dummy IsNill() true
nilPtr *Dummy IsValid() true
func abc() false
notExist property IsValid() false
nil IsValid() false
not exist key of map IsValid() false
*/

Struct Tag 結構體標籤

我們在JSON、SQL、ORM處理序列化或反序列化時, 都需要用到這些tag.
用tag來設定該屬性在處理時應該具備的特殊屬性或行為.

tag格式: Key跟Value用:分隔, 值用”包起來; 不同鍵用一個空格作分隔.

1
`Key1:"Value1" Key2:"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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"encoding/json"
"fmt"
"reflect"
)

type Cat struct {
Name string `json:"name"`
Type int `json:"type" id:"100"`
Money float64 `json:"money"`
Ignore bool `json:"-"` // - 表示會被忽略, 直接賦予該類型的零值
}

func main() {
ins := Cat{
Name: "black",
Type: 1,
}

typeOfCat := reflect.TypeOf(ins)

for i := 0; i < typeOfCat.NumField(); i++ {
fieldType := typeOfCat.Field(i)

fmt.Printf("name: %v ,tag:'%v'\n", fieldType.Name, fieldType.Tag)
}

if catType, ok := typeOfCat.FieldByName("Type"); ok {
fmt.Println(catType.Tag.Get("json"))
fmt.Println(catType.Tag.Get("id"))
}

// 透過Marshal(), 來把資料轉成json字串, 這裡會去讀取json tag
j, err := json.Marshal(ins)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(j))

// 透過Unmarshal(), 來把json字串轉成對應物件, 這裡會去讀取json tag
var jsonBody = []byte(`{"name":"yellow","type":2,"money":200}`)
var cat Cat
err = json.Unmarshal(jsonBody, &cat)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v", cat)
}

/*
name: Name ,tag:'json:"name"'
name: Type ,tag:'json:"type" id:"100"'
name: Money ,tag:'json:"money"'
name: Ignore ,tag:'json:"-"'
type
100
{"name":"black","type":1,"money":0}
{Name:yellow Type:2 Money:200 Ignore:false}
*/

Interface

之前提過實現Interface是透過隱性的向上轉型的方式(Duck typing).
來稍微用反射玩看看, 當然實際不是這樣做的, 效能會大打折扣.
Reflect相當吃效能.

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
47
48
49
50
51
52
package main

import (
"fmt"
"reflect"
)

type CatPtrInterface interface {
MeowPtrReceiver()
}

type CatInterface interface {
MeowReceiver()
}

type Cat struct{}

func (c Cat) MeowReceiver() {
fmt.Println("meow")
}

func (c *Cat) MeowPtrReceiver() {
fmt.Println("meowptr")
}

type DogPtrInterface interface {
Woof()
}

func main() {
catPtrObject := &Cat{}
//catPtrInterfaceType := reflect.TypeOf((*CatPtrInterface)(nil)).Elem()
catPtrInterfaceType := reflect.TypeOf(new(CatPtrInterface)).Elem()
catPtrObjectType := reflect.TypeOf(catPtrObject)
fmt.Println("&Cat{} implement CatPtrInterface? ", catPtrObjectType.Implements(catPtrInterfaceType))

catObject := Cat{}
// 建立一個CatInterface指標nil
catInterfaceType := reflect.TypeOf((*CatInterface)(nil)).Elem()
catObjectType := reflect.TypeOf(catObject)
fmt.Println("Cat{} implement CatInterface? ", catObjectType.Implements(catInterfaceType))

dogInterfaceType := reflect.TypeOf((*DogPtrInterface)(nil)).Elem()
fmt.Println("&Cat{} implement DogPtrInterface? ", catPtrObjectType.Implements(dogInterfaceType))
fmt.Println("Cat{} implement DogPtrInterface? ", catObjectType.Implements(dogInterfaceType))
}
/*
&Cat{} implement CatPtrInterface? true
Cat{} implement CatInterface? true
&Cat{} implement DogPtrInterface? false
Cat{} implement DogPtrInterface? false
*/

題外話
小弟最近把Go升級到1.13版…結果VSCode沒法執行Debug QQ
我的作法是把/usr/loca/go的資料刪除, 重新安裝GO1.12版

鐵人賽連結

分享到