0%

How Reflect Work in Go

之前在博客中有讲Reflect的使用, 然而使用后仍然对于Reflect存在疑惑, 知其然不知其所以然. 所以本文讲如何理解ReflectGo中的工作原理;

前提

这一部分讲述, 理解Reflect所需要的前置知识;
众所周知Reflect提供了程序运行时检查数据结构, 修改数据内容的能力.
Go中, 其依赖两个Feature in Go: Type, Interface.

Type

Go是**静态类型(static type)**语言, 其类型在编译时就已经固定了. 例如:

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

import "fmt"

func main() {
type MyInt int

var i int
var j MyInt
fmt.Printf("Variable i type info is: %T \n", i)
fmt.Printf("Variable j type info is: %T \n", j)
}

输出为:

1
2
Variable i type info is: int 
Variable j type info is: main.MyInt

可以发现, 即使i, j的底层类型(underlying type)一致, 对于Go来说其static type是不一致的;

Interface

Interface Type

Type中存在一个特殊的类型Interface, 其表示某特定方法集.只要某个具体变量concrete variable满足Interface声明的方法集, 那么就可以通过Interface variable接收此concrete variable. 例如io.Reader, io.Writer:

1
2
3
4
5
6
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}

只要实现了Read/Write方法, 就可以使用interface variable接收:

1
2
3
4
5
6
var r io.Reader
tty, err := os.OpenFile("/project/src/github.com/GuardingDog/learnGo/README.md", os.O_RDWR, 0)
if err != nil {
return
}
r = tty

上面的例子中, 将*os.File类型,其实现了Read,的变量tty赋值给了interface variable: r;

Empty Interface Type

Interface中也有一个特殊的类型: 空接口Empty Interface: interface {}, 别名为any. 我们都知道, Empty interface中不包含任何方法, 任意的类型, 都实现了Empty Interface, 也就是说:

Empty Interface Variable可以接收任意Variable;

1
2
3
4
5
6
var i any
tty, err := os.OpenFile("/project/src/github.com/GuardingDog/learnGo/README.md", os.O_RDWR, 0)
if err != nil {
return
}
i = tty

上面通过Empty Interface Variable: i来接收tty当然是没问题的;

Interface Variable Representation

而对于Interface Variable其表现形式为: 值与类型对 (Value, Concrete Type) pair: Interface Variable存储了一个具体的值, 与这个具体的值的类型;

  • 具体的值: 指接口内接收的值;
  • 具体的类型: 指的是这个值所代表的类型, 由于Go是静态类型语言, 这个类型中不仅仅包含有满足当前Interface Type的内容, 也包含有其他类型自定义的类型; 简单说: 这个类型, 就是值的类型, 其具备这个类型中定义的所有内容.下面的例子将解释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"io"
"os"
)

func main() {
var r io.Reader
tty, err := os.OpenFile("/project/src/github.com/GuardingDog/learnGo/README.md", os.O_RDWR, 0)
if err != nil {
return
}
r = tty
fmt.Printf("Variable r type info is: %T \n", r)
var w io.Writer
w = r.(io.Writer)
fmt.Printf("Variable W type info is: %T \n", w)
}

输出为:

1
2
Variable r type info is: *os.File 
Variable W type info is: *os.File

我们发现, 可以通过断言(assert)的方式, 将Reader Interface Variable赋值给Writer Interface Variable. 这是因为Reader Interface Variable: rConcrete type: *os.File也实现了Write方法. 也就是说通过Interface Variable接收Concrete Variable并不会丢失任何concrete type的信息, 其仅仅会约束Interface Variable的调用: 例如r 仅可以调用Read方法.

对于empty interface来说, 则可以直接赋值, 不需要assert: 因为empty interface是所有类型的子集:

1
2
3
...
var i any
i = w

三个反射定律

From Interface value To Reflect Object

Go中的reflect pkg核心Object为: reflect.Type, reflect.Value; 这两种类型, 赋予了编程人员获取Interface Value(Value, Concrete Type) pair的能力.

  • 获取: reflect.Type的方式: reflect.TypeOf

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

    import (
    "fmt"
    "reflect"
    )

    func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
    }

    输出为: type: float64;

    上面的例子中, 通过reflect.TypeOf方法获取了x的类型信息, 并输出.
    然而我们要阐述的是Interace value -> reflect Object的转换, 不是concrete value -> reflect Object
    对于Go有所了解的朋友肯定意识到, reflect.TypeOf(x)一定是做了隐式转换, 将concrete value先转换为了interface value, 随后在对interface value进行了提取其type信息的操作;
    这可以通过reflect.TypeOf的函数签名印证此推论:

    1
    2
    3
    4
    5
    // TypeOf returns the reflection Type that represents the dynamic type of i.
    // If i is a nil interface value, TypeOf returns nil.
    func TypeOf(i any) Type {
    ...
    }

    可以看到reflect.TypeOf的入参类型为empty interface: any. 对于Go的函数调用来说, 其均为值传递, 会复制一份数据, 并赋值给函数入参, 所以上面的示例可以通过伪代码描述为:

    1
    2
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(i = copy(x)))
  • 获取: reflect.Type的方式: reflect.ValueOf

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

    import (
    "fmt"
    "reflect"
    )

    func main() {
    var x float64 = 3.4
    fmt.Println("reflect value:", reflect.ValueOf(x))
    fmt.Println("value string:", reflect.ValueOf(x).String())
    var y string = "SUN"
    fmt.Println("reflect value:", reflect.ValueOf(y))
    fmt.Println("value string:", reflect.ValueOf(y).String())
    }

    输出为:

    1
    2
    3
    4
    reflect value: 3.4
    value string: <float64 Value>
    reflect value: SUN
    value string: SUN

    上面的例子中通过reflect.ValueOf方法, 将interface value转换为reflect.Value; reflect.ValueOf的函数签名入参也是empty interface类型, 其参数调用的过程与reflect.TypeOf是一致的, 这里不赘述;

    需要注意的是:

    reflect.ValueOf获得的是reflect.Value对象, reflect.VauleOf.String获得的是string对象. 如果对于interface value其可以转换为string的话, 则输出value本身, 例如"SUN"; 如果不可以转换的话, 则输出格式是固定的<T Value>, 例如3.4;

reflect也提供了reflect.Value, reflect.Type的操作方法:

  • reflect.Value.Type: 将reflect.Value -> reflect.Type;
  • reflect.Value.Kind, reflect.Type.Kind: 获取reflct.Objectunderlying type;

由于并非本文重点, 这里不一一解释;

From Reflect ObjectTo Interface Value

既然上面可以将Interface value转换为reflect Object, 那么这个过程也可以反转, 其方法为: reflect.Object.Interface; 这里以reflect.Value为例:

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

import (
"fmt"
"reflect"
)

func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
i := v.Interface()
fmt.Println("Interface value:", i)
}

输出为: Interface value: 3.4.
在上面的例子中通过Interface()方法, 将reflect.Value再次封装为(Value, Concrete Type) pair形式的Interface Value, 其类型与值信息并未改变;

如果underlying type类型一致的情况下, 是否会发生类型变动呢? 答案是否定的:

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

import (
"fmt"
"reflect"
)

func main() {
type MyFloat float64
var x MyFloat = 3.4
v := reflect.ValueOf(x)
i := v.Interface()
fmt.Println("Interface value:", i)
fmt.Println("Type of x", reflect.TypeOf(x))
fmt.Println("Type of i", reflect.TypeOf(i))
fmt.Println("Kind of i", reflect.TypeOf(i).Kind())
}

输出为:

1
2
3
4
Interface value: 3.4
Type of x main.MyFloat
Type of i main.MyFloat
Kind of i float64

在上面的例子中, 我们定义了一个自定义类型 MyFloat,并创建了一个变量 x,类型为 MyFloat,值为 3.4。我们通过reflect.Interface方法, 获得了一个Interface Value: i, 并再次检查其类型, 发现其与最初的变量xstatic type一致: MyFloat, 当然啦Underlying typefloat64; 上面的例子说明了Interface value -> reflcet Object -> Interface value的过程是幂等的, 对于Interface Value的值与类型两种信息, 均没有丢失;

Modify a Reflect Object

利用reflect可以修改reflect Object的前提是settable;何为settable?

  • 错误示例:

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

    import (
    "reflect"
    )

    func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    v.SetFloat(7.1) // Error: will panic.
    }

    输出为:

    1
    2
    3
    4
    5
    panic: reflect: reflect.Value.SetFloat using unaddressable value

    goroutine 1 [running]:
    reflect.flag.mustBeAssignableSlow(0x7f2889534108?)
    ....

    上面是示例中, 运行报错; 其提示为: 使用了无法引用的值; 其实这里的报错是不精准的, 其核心是v unsettable状态, 而不是unaddressable;

    我们可以通过CanSet方法, 判定是否可修改:

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

    import (
    "fmt"
    "reflect"
    )

    func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    fmt.Println("settability of v:", v.CanSet())
    }

    输出为:settability of v: false

  • 正确示例:

    我们需要使用 reflect.ValueOf(&x).Elem() 来获取 x 的可设置反射值,然后才能使用 v.SetFloat(7.1) 来修改变量 x 的值。

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

    import (
    "fmt"
    "reflect"
    )

    func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x).Elem()
    fmt.Println("settability of v:", v.CanSet())
    v.SetFloat(7.1)
    fmt.Println("修改后的 x 的值:", x)
    }

    输出为: settability of v: true <br> 修改后的 x 的值: 7.1;

何为settable? 其与addressable很像但不一样, 其表示是否可以通过v找到原始变量的值, 或者说是 reflect.Object是否存储了原始的值;

为何对于reflect.ValueOf(x)就不可以, 对于reflect.ValueOf(&x).Elem就可以呢? 这是由于Go中函数传参是值传递的特性导致的;reflect.ValueOf(x) == reflect.Vaule(i = copy(x)).那么传值本身是绝对无法获取到原始值的, 传指针则有可能获得指针指向的值本身.为了更清晰的表示, 这里再写一个例子:

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() {
var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println("type of v:", v.Type())
fmt.Println("settability of v:", v.CanSet())
y := v.Elem()
fmt.Println("type of y:", y.Type())
fmt.Println("settability of y:", y.CanSet())

y.SetFloat(7.1)
fmt.Println("修改后的 x 的值:", x)
}

输出为:

1
2
3
4
5
type of v: *float64
settability of v: false
type of y: float64
settability of y: true
修改后的 x 的值: 7.1

上面的例子中将reflect.ValueOf.Elem过程拆分为两步: reflect.ValueOf, reflect.ValueOf.Elem, 并分别检验其settable. 发现只要是直接传参的都不可以更改, 只有这种reflect.ValueOf(&x).Elem形式才是找到了原始值存储的地址;

对于struct的修改也是异曲同工, 网上例子很多, 这里不赘述了;

结论

本文介绍了reflect的三定律:

  • From Interface value To `Reflect Object
  • From Reflect ObjectTo Interface Value
  • Modify a Reflect Object

一旦理解了上述过程, 对于实际使用时可以减少不必要的困惑. 当然了, 还是那个建议, 能不用反射就别用;