反射

[TOC]

反射是 Go 语言比较重要的特性。虽然在大多数的应用和服务中并不常见也不提倡使用反射,但是很多框架和底层工具都依赖 Go 语言的反射机制实现简化代码的逻辑。Go 语言的语法元素很少、设计简单,所以它没有特别强的表达能力,但是 Go 语言的 reflect 包能够弥补它在语法上的一些劣势以及作为静态语言的动态特性不足。

Go提供了一种机制在运行时更新变量和检查它们的值, 调用它们的方法, 和它们支持的内在操作, 但是在编译时并不知道这些变量的类型. 这种机制被称为反射.

反射机制就是在运行时动态的调用对象的方法和属性,和C数据结构一样,go对象头部并没有类型指针,在运行期间,无法通过自身获取类型相关的信息。

什么叫反射,为什么要反射?

反射机制:**在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

反射(Reflection)被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

反射机制提供的功能:

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的成员变量和方法;
  5. 生成动态代理。

在计算机科学领域,反射是指一类应用,它们能够自描述自控制。即这类应用通过采用某种机制来实现对自己行为的描述和监测,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

每种语言的反射模型都不同,有些语言根本不就支持反射。GO反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用。

Go语言类型设计

  • 变量包括(type, value)两部分,理解这一点就知道为什么nil != nil了
  • type 包括 static type和concrete type. static type 是平常编码是看见的类型(如int、string),concrete type是runtime系统看见的类型
  • 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.

静态类型

Golang 的指定类型的变量的类型是静态的如 int,string,float 等在创建变量的时候就已经确定。

反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有反射一说。

type ID int

var x int
var y ID
var z string

x变量的静态类型的 int,y的静态类型的ID,虽然它们的底层类型都是int,但是不能直接赋值。

interface{} 类型

为什么只有 interface 类型才有反射一说?

每个interface 变量都有一个对应 pair,pair 中记录了实际变量的值和类型:(value, type)。

一个interface{} 类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。

interface 及其 pair 的存在,是 Golang 中实现反射的前提,理解了pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。

为了获取取到interface变量内部的信息,两个反射的主要方法:reflect.ValueOf() 和 reflect.TypeOf()

  • reflect.TypeOf() 是获取 pair 中的type,即接口 reflect.Type

  • reflect.ValueOf() 获取pair中的value reflect.Value

就是通过 reflect.TypeOf(),reflect.ValueOf() 将“接口类型变量”转换为“反射类型对象”。

interface{} 类型在语言内部是通过 emptyInterface 结体来表示:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
   typ  *rtype   //变量类型
   word unsafe.Pointer
}

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
	size       uintptr
	ptrdata    uintptr  // number of bytes in the type that can contain pointers
	hash       uint32   // hash of type; avoids computation in hash tables
	tflag      tflag    // extra type information flags
	align      uint8    // alignment of variable with this type
	fieldAlign uint8    // alignment of struct field with this type
	kind       uint8    // enumeration for C
	alg        *typeAlg // algorithm table
	gcdata     *byte    // garbage collection data
	str        nameOff  // string form
	ptrToThis  typeOff  // type for pointer to this type, may be zero
}

rtype类型实现了反射包reflect.Type接口。

反射三定律

​ 1.反射可以将“接口类型变量”转换为“反射类型对象”。

​ 2.反射可以将“反射类型对象”转换为“接口类型变量”。

​ 3.如果要修改“反射类型对象”,其值必须是“可写的”(settable)。

反射是基于interface{} 类型的,是将interface类型中的值和类型:(value, type) 转化为反射对象reflect.Value reflect.Type 对象。这是反射的前提。

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。会把 type 和 value 信息打包并填充到一个接口变量中。

对于一个不具有“可写性”的 Value类型变量,调用 Set 方法会报出错误,只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

reflect 包

反射是由reflect 包提供支持,reflect 包实现了运行时反射,它定义了两个重要的类型, Type 和Value.

  • 一个Type 表示一个Go类型. 它是一个接口, 有许多方法来区分类型和检查它们的组件, 例如一个结构体的成员或一个函数的参数等. 需要了解变量类型信息的,可以通过Type 来获取
  • Value 一个结构体,

在Go语言的实现中,一个interface类型的变量存储了2个信息, 一个<值,类型>对。 反射操作所需的信息全部源自接口变量,interface变量存储自身类型和实际对象类型数据。Go反射主要由reflect支持。主要来自reflect包两个反射入口函数reflect.Type()reflect.Value()

reflect包两个反射入口函数reflect.Type()reflect.Value()可以将一个普通的变量转换成『反射』包中提供的 TypeValue,随后就可以使用反射包中的方法对它们进行复杂的操作。

// 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 interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i))
   return toType(eface.typ)
}

TypeOf.TypeOf()将一个 interface{} 变量转换成了内部的 emptyInterface 表示,然后从中获取相应的类型信息。

reflect.Type

函数 reflect.TypeOf() 接受任意的 interface{} 类型, 并以reflect.Type形式返回其动态类型

// Type is the representation of a Go type.
type Type interface {
	Align() int
	FieldAlign() int
	MethodByName(string) (Method, bool)
  //根据序号获取对象的方法
  Method(int) Method
  //获取导出方法数量
	NumMethod() int
  //返回类型名
	Name() string
	// PkgPath returns a defined type's package path,
	PkgPath() string
	// Size returns the number of bytes needed to store
	Size() uintptr
	// String returns a string representation of the type.
	String() string
	// Kind 方法 会返回底层数据的类型,而不是静态类型。
	Kind() Kind
	//判断当前类型是否实现了某个接口
	Implements(u Type) bool
	//AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool
	ConvertibleTo(u Type) bool
	Comparable() bool
	Bits() int
	ChanDir() ChanDir
	IsVariadic() bool
	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
	Elem() Type
	Field(i int) StructField
	// FieldByName returns the struct field with the given name
	FieldByIndex(index []int) StructField
	FieldByName(name string) (StructField, bool)
	FieldByNameFunc(match func(string) bool) (StructField, bool)
	In(i int) Type
	// Key() returns a map type's key type.
	Key() Type
	// Len returns an array type's length.
	Len() int
	// NumField returns a struct type's field count.
	NumField() int
	// NumIn returns a function type's input parameter count.
	NumIn() int
	// NumOut returns a function type's output parameter count.
	NumOut() int
	Out(i int) Type
	common() *rtype
	uncommon() *uncommonType
}
  • 区分Type和Kind,即静态类型(真实类型)和基础接口类别(底层结构)
type X int

func rDemo2() {
	var a X = 20
	t := reflect.TypeOf(a)
	fmt.Println(t.Name()) // X 真实类型(静态类型)
	fmt.Println(t.Kind()) // int 底层类型
}
  • fmt.Printf 提供了一个简短的%T 标志参数, 内部使用reflect.TypeOf 的结果输出.

reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法. 它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值:

v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface()      // an interface{}
i := x.(int)            // an int
fmt.Printf("%d\n", i)   // "3"

reflect.Value 和 interface{} 都能装载任意的值。相比之下, 一个 Value 则有很多方法来检查其内容。

reflect.Value

reflect 包中另一个重要的类型是 Value. 与 Type 不同的是,它被声明成了结构体。

一个 reflect.Value 可以装载任意类型的值. 函数 reflect.ValueOf 接受任意的 interface{} 类型, 并返回一个装载着其动态值的 reflect.Value.

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
   if i == nil {
      return Value{}
   }

   //reflect.escapes 函数保证当前值逃逸到堆上,为何要逃逸到堆上
   escapes(i)
   //reflect.unpackEface 方法从接口中获取 Value 结构体:
   return unpackEface(i)
}

// 将传入的接口转换成 emptyInterface 结构体,然后将具体类型和指针包装成 Value 结构体并返回。
func unpackEface(i interface{}) Value {
	e := (*emptyInterface)(unsafe.Pointer(&i))
	// NOTE: don't read e.word until we know whether it is really a pointer or not.
	t := e.typ
	if t == nil {
		return Value{}
	}
	f := flag(t.Kind())
	if ifaceIndir(t) {
		f |= flagIndir
	}
	return Value{t, e.word, f}
}
type Da struct {
	Page 		int
	PageSize 	int
}

func demo3() {
	d := Da{
		1,
		10,
	}
	v := reflect.ValueOf(d)
	fmt.Println(v.String())	//<reflect1.Da Value>
	fmt.Println(v.Kind())	//struct
	fmt.Println(v.String())

	// FieldByName returns the struct field with the given name.
	fmt.Println(v.FieldByName("PageSize")) 
}

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Type

将传入对象转变为接口(interface{})类型.只有interface类型才有反射一说.

反射能在运行期间探知对象类型信息和内部结构,一定程度上弥补了静态语言在动态行为上的不足。

反射性能

//反射性能
type Data struct {
	X int
}

var d = new(Data)

func set(x int) {
	d.X = x
}

func rset(x int) {
	v := reflect.ValueOf(d).Elem()
	f := v.FieldByName("X")
	f.Set(reflect.ValueOf(x))
}

var v reflect.Value =  reflect.ValueOf(d).Elem()
var f = v.FieldByName("X")
func rset1(x int) {
	f.Set(reflect.ValueOf(x))
}

func BenchmarkSet(b *testing.B) {
	for i := 0; i < b.N; i++ {
		set(100)
	}
}

func BenchmarkRSet(b *testing.B) {
	for i := 0; i < b.N; i++ {
		rset(100)
	}
}

func BenchmarkRSet1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		rset1(100)
	}
}

//运行 go test -bench=. -run=none
//BenchmarkSet-8                  1000000000               0.409 ns/op
//BenchmarkRSet-8                 10349564               115 ns/op
//BenchmarkRSet1-8                32448304                37.8 ns/op

Go 动态类型

和其它语言相比,Go语言结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。

像 Python,Ruby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析。Go语言的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{} 这样的“泛型”上,可以通过类型断言来检查变量是否实现了相应接口。

函数 reflect.TypeOf 接受任意的 interface{} 类型, 并以reflect.Type 形式返回其动态类型

func DynamicType() {
	var a ID = 1
	var b int = 2

	at := reflect.TypeOf(a)
	bt := reflect.TypeOf(b)

	fmt.Println(at.Kind())   	// int  Kind 方法返回具体类型
	fmt.Println(at.String())	// reflect1.ID

	fmt.Println(bt.Kind())		// int
	fmt.Println(bt.String())  	// int
}

问题思考

什么是反射?反射用来解决什么问题?

  • 反射是语言的自描述自控制,采用某种机制来实现对自己行为的描述和监测。GO反射机制就是在运行时动态的调用对象的方法和属性。
  • 如可以通过反射获取结构体的所有字段信息,字段的标签,类型是否可比较,值是否可寻址,slice 的元素信息,获取 map 所有的 key 信息等等。
  • 在 Go 中,反射主要通过 reflect 包的实现,reflect.Type 和reflect.Value 是两个核心的接口。

  • 反射可以弥补静态语言,的动态特性的不足。在一些场景,通过反射可以大大减少代码量。
  • 在一些不能确定函数,不能确定参数的,需要动态处理任意对象的场景,可以使用发射大大节省代码量。

列举一些使用到反射的场景?

  • 标准库 json 序列化,通过 json 获取结构体的信息,如 tag 信息,来进行序列化。
  • 标准库 RPC 库,也是通过反射来构建 RPC 的函数调用的。
  • ORM 对象关系映射 也有用到。

反射的弊端?

  • 反射增加了程序的不可靠性,反射很多值和类型需要在运行时才能确定,相比其他静态类型的,很多问题无法通过静态检测发现。
  • 反射的性能比较差。

都说反射慢,为什么慢?

  • 反射中大量使用的枚举和for循环,有的地方甚至是三层for循环,时间复杂度太高,对于高频的场景来说,会大大影响性能。
  • 通过反可以增加了语言的动态特性,需要在运行时动态的确定类型和值,这大大增加了实现的指令开销和复杂度。
  • 通过反射,增加了很多的内存分配和对象开销,增大的GC的压力。
  • TODO

更新时间: