反射
请远离reflect和unsafe包,除非你确实需要它们。原因有三
- 基于反射的代码是比较脆弱的。反射在真正运行代码时才可能抛出异常, 可能是写完代码很久以后, 而且程序也可能运行了很长的时间。
- 即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档。
- 基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以当反射能使程序更加清晰的时候可以考虑使用。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。
Go语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。
1. 为什么需要反射?
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
|
// Sprint 这是仿的 fmt 包的同名函数,功能不全, 且如果输入 map/array 等类型
// 分支还需要补充这些代码,若是需要导入的组合类型,将会产生依赖
// 所以需要反射
func Sprint(x interface{}) string {
type stringer interface {
String() string
}
switch x := x.(type) {
case stringer:
return x.String()
case string:
return x
case int:
return strconv.Itoa(x)
case bool:
if x {
return "true"
}
return "false"
default:
// array, chan, func, map, pointer, slice, struct
return "???"
}
}
|
2. 如何使用?
2.1. TypeOf
函数 reflect.TypeOf
接受任意的 interface{} 类型,并以 reflect.Type
形式返回其动态类型
1
2
3
|
t := reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"
|
因为 reflect.TypeOf 返回的是一个动态类型的接口值,它总是返回具体的类型。
1
2
|
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"
|
fmt.Printf 提供了一个缩写 %T 参数,内部使用 reflect.TypeOf 来输出
1
|
fmt.Printf("%T\n", 3) // "int"
|
2.2. ValueOf
和 reflect.Type 类似,reflect.Value 也满足 fmt.Stringer 接口,但是除非 Value 持有的是字符串,否则 String 方法只返回其类型。而使用 fmt 包的 %v 标志参数会对 reflect.Values 特殊处理。
1
2
3
4
|
v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // note :"<int Value>"
|
如何判断类型?
1
2
|
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"
|
如何获取对象?
它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值
1
2
3
4
|
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
|
2.3. 使用反射判断类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
// 空的 reflect.Value 的 kind 即为 Invalid
return "Invalid"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
// 将字符串 s 转换为“双引号”引起来的字符串
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice,
reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default:
return v.Type().String() + " value"
}
}
|
2.4. 调用方法
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
|
//------------- 使用反射调用对象方法 -----------------
type Employee struct {
EmployeeID string
Name string `format:"normal"`
Age int
}
func (e *Employee)UpdateAge(newVal int ) {
e.Age = newVal
}
type Customer struct {
CookieId string
Name string
Age int
}
func TestInvokeByName(t *testing.T){
e := &Employee{"1","Mike",30}
//t.Log("Name:value(%[1]v), Type(%[1]T)",reflect.ValueOf(*e).FieldByName("Name"),
// reflect.ValueOf(*e).FieldByName("Age"))
if nameField,ok:=reflect.TypeOf(*e).FieldByName("name"); ok {
t.Error("Failed to get `name` field.")
}else{
t.Log("Tag:format", nameField.Tag.Get("format"))
}
// 调用方法
reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)})
t.Log("update age:",e)
}
|
深度比较
map 不能直接使用等于比较, 可以使用 反射的 reflect.DeepEqual(s1,s2)
进行比较, 也适用于切片