23.反射

反射

请远离reflect和unsafe包,除非你确实需要它们。原因有三

  1. 基于反射的代码是比较脆弱的。反射在真正运行代码时才可能抛出异常, 可能是写完代码很久以后, 而且程序也可能运行了很长的时间。
  2. 即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档。
  3. 基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以当反射能使程序更加清晰的时候可以考虑使用。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。

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) 进行比较, 也适用于切片

Licensed under CC BY-NC-SA 4.0
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy