21天从Java转向Go之第十二天——水滴石穿(接口与反射)

反射的含义

  • 对于一个类型变量,它有两层含义,一是类型是声明,二是其存储的值是什么。类型决定了变量的存放方式、支持的操作集和方法集。对值的无外乎读和写,值在内存中都以0、1的格式存放的,具体0、1被解释成什么还需要类型的支持。类型和值不是孤立的,Go语言提供了反射功能,支持程序动态地访问变量的类型和值。
  • Go语言反射的基础是把编译器和运行时把类型信息以合适的数据结构保存在可执行程序中。Go语言提供的reflect标准库只是为语言使用者提供一套访问接口。
  • 接口有静态类型和动态类型。静态类型是接口在定义时就确定的。接口的静态类型本质上是接口的方法签名集合。动态类型是接口绑定的具体实例的类型。接口可以绑定不同的实例,所以动态类型是随着其绑定的不同类型实例而发生变化的。
  • Go语言提供了类型断言和接口类型查询来确认已经初始化的接口变量指向实例的具体类型是什么。类型断言和接口类型查询只是语法格式上的不同,具有相同的语义。接口断言 :
o := i.(TypeName)   
if o,ok := i.(TypeName){

}

接口类型查询:

switch v:= i.(TypeName){
  case type1:
    xxx
  case type2:
    xxx
  default:
    xxx
}

Go语言空接口的用途

  • GO语言没有泛型。如果一个函数可以接收任意类型,则使用空接口类型,弥补没有泛型的一种手段。
  • 反射:空接口是反射实现的基础。反射库就是将相关具体的类型转换并赋值给空接口后才去处理。
  • 空接口的比较:空接口有两个字段,一个是实例类型,另一个是指向绑定实例的指针,只有两个都为nil时,空接口才为nil。

接口的数据结构

  • 接口变量必须初始化才有意义,没有初始化的接口变量的默认值是 nil,没有任何意义。具体类型实例传递给接口称为接口的实例化。在接口的实例化的过程中,编译
    器通过特定的数据结构描述这个过程。

非空接口的数据结构

  • 数据结构是iface,在src/runtime/runtime2.go中定义
type iface struct {
	tab  *itab
	data unsafe.Pointer
}
  • itab:存放接口自身类型和绑定的实例类型及其实例相关的函数指针。
  • 数据指针data:指向接口绑定的实例副本,接口的初始化也是一种值拷贝。data指向的是实例的副本,如果传递给接口的是指针类型,则data指向指针的副本。总而言之,无论接口的转换,还是函数调用,Go遵循一样的规则——值传递。
type itab struct {
	inter *interfacetype  //接口自身的静态类型
	_type *_type //存放具体实例类型(动态类型)
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
  • itab这个数据结构是非空接口实现动态调用的基础,itab的信息被编译器和连接器保存下来,存放在可执行文件的只读存储段.rodata中。itab存放的静态分配的存储空间中,不受GC的限制,其内存不会被回收。
  • Go语言是一种强类型语言,编译器在编译时会做严格类型校验。所以Go必然为每种类型维护一个类型的元信息。这个元信息在运行和反射时都会用到,Go语言的类型元信息的通用结构是_type(src/runtime/type.go),其他类型都是以_type为内嵌字段封装而成的结构体。
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}
  • 接口的动态调用过程,有两部分多余时耗:一是接口实例化的过程,也就是iface结构建立的过程,一旦实例化,这个接口和具体类型的itab数据结构是可以复用的。另一个是方法的调用,是一个函数指针的间接调用,接调用是一种动态的计算后的跳转调用。

空接口的数据结构

type eface struct {
	_type *_type
	data  unsafe.Pointer
}
- 空接口是没有任何方法集的接口,所以空接口内部需要维护和动态内存分配相关的数据结构。空接口只关心存放的具体类型是什么。空接口保留了具体实例的类型和值拷贝。

反射本质

  • Go的反射基础是接口和类型系统。Go反射巧妙地借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构eface,反射基于这个转换后的数据结构来访问和操作实例的值和类型。
  • 反射包里面有个通用的描述类型公共信息的结构rtype,这个和接口内部实现时的 runtime 里面的type是同一个东西,只是因为包的隔离性分开定义而己,都是描述类型的通用信息 ,同时为每一种基础类型封装了一个特定的结构。
package main

import (
	"fmt"
	"reflect"
)

type humnan struct {
	age int
	name string
}

func main() {
	s:=[]int{1,2,3}
	kind := reflect.TypeOf(s).Kind()
	fmt.Println(reflect.TypeOf(s))  //[]int
	fmt.Println(reflect.ValueOf(s)) //[1 2 3]
	fmt.Println(kind) //slice
        fmt.Println(reflect.TypeOf(s).Elem()) //int

	s1:="hello"
	fmt.Println(reflect.TypeOf(s1))  //string
	fmt.Println(reflect.ValueOf(s1)) //hello
	fmt.Println(reflect.TypeOf(s1).Kind()) //string

	h := humnan{name: "xiaomi", age: 10}
	fmt.Println(reflect.TypeOf(h))  //string
	fmt.Println(reflect.ValueOf(h)) //hello
	fmt.Println(reflect.TypeOf(h).Kind()) //string
}

  • 对于reflect.TypeOf(a)(传进去的实参有两种类型,一种是接口变量,另一种是具体体类。如果是具体类型变量,则reflect.TypeOf()返回的是具体的类型信息:如果是接口变量,则函数的返回结果又分两种情况:如果绑定了具体类型实例,返回的是接口的动态类型,也就是绑定的具体实例类型的信息,如果没有绑定具体类型实例,则返回的是接口自身的静态类型信息。

基础类型

  • Type接口有一个Kind()方法,返回的是一个整数枚举值,不同的值代表不同的类型。这里的类型是一个抽象的概念,暂且称之为“基础类型”,比如所有的结构都归结为一种基础类型 struct ,所有的函数都归结为一种基础类型func 。基础类型是根据编译器、运行时构建类型的内部数据结构不同来划分的,不同的基础类型,其构建的最终内部数据结构不一样。具体类型可以定义成千上万种,单单 struct 就可以定义出很多新类型,但是它都归结为一种基础类型struct。基础类型是抽象的,其种类有限,总共定义了 26 种基础类型。
type Kind uint

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)
posted @ 2021-12-02 13:32  永恒之光  阅读(78)  评论(0编辑  收藏  举报