Golang 反射应用及源码分析

reflect简介

  • reflect 是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力
  • 用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为
  • reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。
  • 反射作为一种元编程方式可以减少重复代码,但使用不当,也会使程序变得过于抽象,难以理解,且运行缓慢。因此我们需要对反射有一个基本的了解,以便能够恰当的使用反射。

什么时候需要反射

  • 不能预先确定参数的类型,可能是因为没有约定好,也可能是传入的参数类型有很多,且不能统一表示。
  • 函数需要根据输入的参数来动态的执行不同的行为

反射的优缺点

反射的优点

  • 可以在一定程度上避免硬编码,提供灵活性和通用性。
  • 可以作为一个第一类对象发现并修改源代码的结构(如代码块、类、方法、协议等)。

反射的缺点

  • 同样因为反射的概念和语法都比较抽象,滥用反射会使得代码难以被其他人读懂
  • 无法在编译时检查错误。作为一种强类型的语言,go 的编译器会在编译阶段检查出类型错误,但是 interface 定义的类型是不明确的,代码在运行时存在 panic 的风险
  • 降低了代码运行的效率,反射出变量的类型需要额外的开销

反射三大定律

  • Reflection goes from interface value to reflection object.
  • Reflection goes from reflection object to interface value.
  • To modify a reflection object, the value must be settable.

Reflection goes from interface value to reflection object.

接口值反射到反射对象

  • 从 interface{} 变量可以反射出 reflect 对象,
  • 具体来说,通过 reflect.TypeOf 获取了变量的类型,reflect.ValueOf 获取了变量的值。
  • 如果我们知道了一个变量的类型和值,那么就意味着我们知道了这个变量的全部信息。通过 type、value 提供的方法,可以获取变量实现的方法、类型的全部字段等等。

img

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

import (
"fmt"
"reflect"
"testing"
)

type Coder struct {
Name string
}

func (c *Coder) String() string {
return c.Name
}

func TestCoder(t *testing.T) {
coder := &Coder{Name: "obgnail"}
typ := reflect.TypeOf(coder)
val := reflect.ValueOf(coder)

typeOfStringer := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
fmt.Println("kind of coder:", typ.Kind()) // kind of coder: ptr
fmt.Println("type of coder:", typ) // type of coder: *reflect_test.Coder
fmt.Println("value of coder:", val) // value of coder: obgnail
fmt.Println("implements stringer:", typ.Implements(typeOfStringer)) // implements stringer: true
}

Reflection goes from reflection object to interface value.

反射对象 变回 interface{}对象

  • 具体来说,通过 Interface()方法 从反射对象获取interface{} 变量

img

To modify a reflection object, the value must be settable.

  • 要更新一个 reflect.Value,那么它的值必须是可被更新的。否则将会导致 panic。

  • 辅助判断:

    • reflect.flag.mustBeAssignable 检查是否可以被设置
    • reflect.flag.mustBeExported 检查变量是否可以被导出,防止不可导出的的变量泄露
    • reflect.Value.assignTo 会返回一个新的反射对象,这个返回的反射对象指针会直接覆盖原反射变量