Hike News
Hike News

Golang物件導向初識-day20-反射(reflect)

Introduction

  • 反射,是指電腦程序在運行時(Run Time)可以訪問、檢測和修改它本身狀態或行為的一種能力。
    • 程序在運行時能夠觀察並修改自己的行為
  • 可以在運行時動態的獲取變量的相關信息,包括類型
  • 在Golang中有提供官方的package可實現反射
  • 主要有兩個函數
    1. reflect.TypeOf:獲取變量的類型,返回reflect.Type類型
    2. reflect.ValueOf:獲取變量的值,返回reflect.Value類型
      • reflect.Value.Kind:可以獲取變量的類別,返回一個常量
      • reflect.Value.Interface{}:轉換成interface{}(空接口)類型

TypeOf

動態的獲取變量的類型

1
func TypeOf (i interface{}) Type

  • 可看到reflect.TypeOf接受空接口類型的參數,並返回reflect.Type類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"reflect"
)

//空接口接受任何類型
func test(b interface{}){

//獲取傳參的類型
tp := reflect.TypeOf(b)

fmt.Println("b type:",tp)
fmt.Printf("tp type: %T",tp)
}

func main(){
a := 100
test(a)
}

result

1
2
b type: int
tp type: *reflect.rtype
  • 透過reflect.TypeOf可直接獲取傳參進來的空接口為何種類型,但儲存類型的變量是reflect.Type類型

ValueOf

  • 透過reflect.ValueOf函數可將空接口變量轉換成reflect.Value類型
  • reflect.Value類型擁有很多方法,可獲取變量的相關信息(分析變量)
1
func ValueOf (i interface{}) Value
  • 可看到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 test(b interface{}){
vl := reflect.ValueOf(b)
fmt.Println("b value:",vl)
fmt.Printf("vl type: %T",vl)
}

func main(){
a := 100
test(a)
}

result

1
2
b value: 100
vl type: reflect.Value
  • 可看到其返回的類型為reflect.Value類型

.Kind() 查看變量類別

  • reflect.Value類型的變量可調用.Kind()方法查看變量的類別
  • 返回的是變量的類別,返回一個常量(reflect.類別)
  • 請注意類別類型不一樣
1
2
3
4
5
6
type Student struct{
Name string
Age int
}

a := Student{}
  • a變量為自訂義的Student類型
  • a變量的類別結構體(struct)

example

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

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
}

func test(b interface{}){
tp := reflect.TypeOf(b)
fmt.Println("b type:",tp)


vl := reflect.ValueOf(b)
fmt.Println("b value:",vl)
l := vl.Kind()
fmt.Printf("b kind: %v\n",l)
}

func main(){
a := Student{
Name : "Curtis",
Age : 18,
}

test(a)
}

result

1
2
3
b type: main.Student
b value: {Curtis 18}
b kind: struct

.interface() 轉換成空接口類型

  • reflect.Value類型的變量可調用.interface()方法將類型轉換成interface{}類型
  • interface{}空接口類型又可透過類型斷言,將變量從空接口轉成具體類型

example

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

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
}

func test(b interface{}){
vl := reflect.ValueOf(b)
fmt.Println("b value:",vl)

//使用ValueOf獲取值時,類型為reflect.Value類型
fmt.Printf("vl type: %T\n",vl)

//reflect.Value類型的變量可調用interface()方法,將變量轉成interface{}類型
interfacE := vl.Interface()

//透過安全的類型斷言,將interface{}類型轉成變量的具體類型
stu,ok := interfacE.(Student)
if ok {
fmt.Printf("vl.interface().type:%T\n",stu)
}
}

func main(){
a := Student{
Name : "Curtis",
Age : 18,
}

test(a)
}

result

1
2
3
b value: {Curtis 18}
vl type: reflect.Value
vl.interface().type:main.Student

獲取變量的值

reflect.Value類型可使用內置的方法,獲取變量的值

1
2
3
4
reflect.ValueOf(x).Float()
reflect.ValueOf(x).Int()
reflect.ValueOf(x).String()
reflect.ValueOf(x).Bool()
  • 根據值不同的類型,獲取其值

要是空接口傳進來的值為int類型,透過reflect.ValueOf()函數轉成reflect.Value類型
若使用reflect.Value類型可調用的String()方法獲取一開始具體類型int的值
則會獲取不到相對應的值

example

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

import (
"fmt"
"reflect"
)

func test(b interface{}){
vl := reflect.ValueOf(b)

fmt.Printf("vl.Int(): %d\n",vl.Int())
fmt.Printf("vl.String(): %s\n",vl.String())
}

func main(){
a := 100
test(a)
}

result

1
2
vl.Int(): 100
vl.String(): <int Value>
  • 因從b參數傳進來的類型為int類型,將其轉成string類型時獲取不到值

設置變量的值

reflect.Value類型可使用內置的方法,設置變量的值

  • 其方法通常為Set類型作為方法名
  • 只有reflect.Value的類型可以調用
  • 注意!!!欲設置的值應為外部變量,因此傳參需傳入指針類型,否則panic
  • 需搭配Elem()方法設置指針變量內部之值,相當於具體類型的*
1
2
3
4
reflect.ValueOf(x).Elem().SetFloat()    
reflect.ValueOf(x).Elem().SetInt()
reflect.ValueOf(x).Elem().SetString()
reflect.ValueOf(x).Elem().SetBool()

example

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

import (
"fmt"
"reflect"
)

func test(b interface{}){
val := reflect.ValueOf(b)
val.Elem().SetInt(10000) //[1]

//指針在取值時須加上*,這邊仍適用Elem()代替*
c := val.Elem().Int()
fmt.Println("c =",c)
}

func main(){
a := 100
test(&a)
fmt.Println("a =",a)
}

[1] Elem()函數為reflect.Value類型可調用的方法
是向傳進來具體類型的指針指向的地址取值,相當於*

1
2
var x *int = new(int)
*x = 100
  • Elem()就類似於變量前面加上*

result

1
2
c = 10000
a = 10000

操作結構體

  • reflect.Value類型可使用內置的方法,操作結構體
  • 其傳進來的空接口必須指向的是結構體類別,才能操作

.NumField() 查看結構體的字段數

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

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
score float64
}

func StructOP(b interface{}){
val := reflect.ValueOf(b)

c := val.Kind() //查看參數的類別為何

// 判斷是否為struct 不是struct類別則退出
// reflect.Struct 為一常量
if c != reflect.Struct{
fmt.Println("parameter isn't belong with a struct")
return
}
num_property := val.NumField()
fmt.Println(num_property)
return
}

func main(){
Tom := Student{
Name : "Tom",
Age : 100,
score: 60.0,
}

StructOP(Tom)
}

result

1
3

tips

  • reflect.Value類型亦能調用.Field(n) 方法顯示字段信息,
    • n為第n個字段
    • 若傳入為struct的指針類型,記得使用Elem().Field(n)方法顯示字段信息
    • 字段的信息仍為reflect.Value類型,仍可對字段進行動態分析,或對字段進行操作
    • 透過Elem().Field(n).Set類型(值)便可以對外部struct中的字段修改值

.NumMethod() 查看結構體的方法數

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

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
score float64
}

//方法1
func (self *Student)Reading(){
fmt.Println("Reading Books")
}

//方法2
func (self *Student)Write(){
fmt.Println("Writing")
}

func StructOP(b interface{}){
val := reflect.ValueOf(b)

c := val.Elem().Kind() //注意指針類型取值使用Elem()
if c != reflect.Struct{
fmt.Println("parameter isn't belong with a struct")
return
}

num_Method := val.NumMethod() //*
fmt.Println(num_Method)
}

func main(){
Tom := Student{
Name : "Tom",
Age : 100,
score: 60.0,
}

StructOP(&Tom)
}

result

1
2

.Method(n).Call 調用結構體中的方法

  • n代表的是調用第n個方法
  • 使用Call方法時須先初始化一個reflect.Value類型的切片,或直接傳入nil
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
package main

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
score float64
}

func (self *Student)Reading(){
fmt.Println("Reading Books")
}

func (self *Student)Write(){
fmt.Println("Writing")
}

func StructOP(b interface{}){
val := reflect.ValueOf(b)

c := val.Elem().Kind()
if c != reflect.Struct{
fmt.Println("parameter isn't belong with a struct")
return
}

//使用Call方法時必須先初始化一個reflect.Value類型的切片
var method_slice = make([]reflect.Value,0)

//調用結構體的第一個方法
val.Method(0).Call(method_slice)

//調用結構體的第二個方法,Call函數的參數可傳入nil
val.Method(1).Call(nil)

}

func main(){
Tom := Student{
Name : "Tom",
Age : 100,
score: 60.0,
}

StructOP(&Tom)
}

result

1
2
Reading Books
Writing

Tag

在之前json的打包中,我們遍歷結構體時,透過json作為key找到其value作為結構體的字段名
其具體也是透過反射做到的

  • 調用Tag方法必須為reflect.Type類型
  • Tag的類型為reflect.StructTag類型,可調用Get()方法尋找key對應的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
package main

import (
"reflect"
"fmt"
)

type Car struct{
Name string `json:"car_name"`
Age int
Price float64
}

func main(){
Benz := &Car{
Name : "Benz",
Age : 20,
Price : 10000.00,
}

typ := reflect.TypeOf(Benz)

//獲取第0個字段的Tag
tag := typ.Elem().Field(0).Tag

//其類型為reflect.StructTag類型
fmt.Printf("tag type = %T\n",tag)

//使用Get()函數中的參數為key值,返回對應的value
fmt.Println("tag =",tag.Get("json"))
}

result

1
2
tag type = reflect.StructTag
tag = car_name
  • json就是利用反射,得到tag的key為”json”,其對應的value 優先進行打包