GoLang反射机制深入讲解

目录
  • 反射
    • 反射类型Type
      • 指针
      • 结构体
    • 反射值Value
      • 结构体
      • 空与有效性判断
      • 修改值
      • 函数调用
    • 反射三定律
  • interface
    • 底层结构
      • iface
      • eface

反射

Go语言提供了reflect 包来访问程序的反射信息;定义了两个重要的类型Type和Value:

  • reflect.TypeOf:获取任意值的类型对象(reflect.Type);
  • reflect.ValueOf:获得值的反射值对象(reflect.Value);

反射类型Type

Go语言程序中的类型(Type)指的是系统原生数据类型(如 int、string、bool、float32 等),以及使用 type 关键字定义的类型;而反射种类(Kind)是指对象的归属分类:

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

指针

对指针指向的对象,可通过reflect.Elem() 方法获取这个指针指向的元素类型(等效于对指针类型变量做了一个*操作)。

func reflectStruct() {
	type Cat struct {
	}
	aCat := &Cat{}
	// 获取结构体实例的反射类型对象
	typeOfCat := reflect.TypeOf(aCat)
	fmt.Printf("ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
	// 取类型的元素
	typeOfCat = typeOfCat.Elem()
	fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
	typeOfCat = reflect.TypeOf(*aCat)
	fmt.Printf("*ptr name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
// ptr name:'' kind:'ptr'
// element name: 'Cat', element kind: 'struct'
// *ptr name:'Cat' kind:'struct'

结构体

对结构体对象,获取对象信息后,可通过NumField() 和 Field() 方法获得结构体成员的详细信息。

方法 说明
Field(i int) StructField 根据索引返回索引对应字段的信息
NumField() int 返回结构体成员字段数量
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据匹配函数匹配需要的字段

字段信息中含有:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段在结构体中的路径
    Type      Type       // 字段的反射类型 reflect.Type
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // FieldByIndex中的索引顺序
    Anonymous bool       // 是否为匿名字段
}

获取字段的名称与tag:

func reflectStructField()  {
	// 声明一个空结构体
	type Cat struct {
		Name string
		// 带有结构体tag的字段
		Type int `json:"type" id:"100"`
	}
	aCat := Cat{Name: "mimi", Type: 1}
	// 获取结构体实例的反射类型对象
	typeOfCat := reflect.TypeOf(aCat)
	// 遍历结构体所有成员
	for i := 0; i < typeOfCat.NumField(); i++ {
		fieldType := typeOfCat.Field(i)
		fmt.Printf("Field-%v: name: %v  tag: '%v'\n", i, fieldType.Name, fieldType.Tag)
	}
	// 通过字段名, 找到字段类型信息
	if catType, ok := typeOfCat.FieldByName("Type"); ok {
		fmt.Println("Field Tag: ", catType.Tag.Get("json"), catType.Tag.Get("id"))
	}
}
// Field-0: name: Name  tag: ''
// Field-1: name: Type  tag: 'json:"type" id:"100"'
// Field Tag:  type 100

反射值Value

通过下面几种方法从反射值对象 reflect.Value 中获取原值

方法名 说 明
Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回

通过反射获取变量的值:

func reflectValue()  {
	var a int = 1024
	// 获取变量a的反射值对象
	valueOfA := reflect.ValueOf(a)
	// 获取interface{}类型的值, 通过类型断言转换
	var getA int = valueOfA.Interface().(int)
	// 获取64位的值, 强制类型转换为int类型
	var getA2 int = int(valueOfA.Int())
	fmt.Println(getA, getA2)
}
// 1024 1024

结构体

反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问:

方 法 备 注
Field(i int) Value 根据索引,返回索引对应的结构体成员字段的反射值对象
NumField() int 返回结构体成员字段数量
FieldByName(name string) Value 根据给定字符串返回字符串对应的结构体字段,没有找到时返回零值
FieldByIndex(index []int) Value 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值; 没有找到时返回零值
FieldByNameFunc(match func(string) bool) Value 根据匹配函数匹配需要的字段,没有找到时返回零值

空与有效性判断

反射值对象(reflect.Value)提供一系列方法进行零值和空判定:

方 法 说 明
IsNil() bool 是否为 nil,只对通道、函数、接口、map、指针或切片有效(否则会panic)
IsValid() bool 是否有效,当值本身非法时(不包含任何值,或值为 nil),返回 false

修改值

通过反射修改变量值的前提条件之一:这个值必须可以被寻址,简单地说就是这个变量必须能被修改。结构体成员中,如果字段没有被导出,即便也可以被访问,也不能通过反射修改。

方法名 备 注
Elem() Value 取值指向的元素值(类似于*操作);对指针或接口时发生panic
Addr() Value 对可寻址的值返回其地址(类似于&操作);当值不可寻址时发生panic
CanAddr() bool 表示值是否可寻址
CanSet() bool 返回值能否被修改;要求值可寻址且是导出的字段

修改结构体字段的值(需要结构体地址,与导出字段):

func reflectModifyValue()  {
	type Dog struct {
		LegCount int
	}
	// 获取dog实例地址的反射值对象
	valueOfDog := reflect.ValueOf(&Dog{})
	// 取出dog实例地址的元素
	valueOfDog = valueOfDog.Elem()
	vLegCount := valueOfDog.FieldByName("LegCount")
	vLegCount.SetInt(4)
	fmt.Println(vLegCount.Int())
}

函数调用

如果反射值对象(reflect.Value)为函数,可以通过Call()调用:参数使用反射值对象的切片[]reflect.Value构造后传入,返回值通过[]reflect.Value返回。

func add(a, b int) int {
	return a + b
}
func reflectFunction() {
	// 将函数包装为反射值对象
	funcValue := reflect.ValueOf(add)
	// 构造函数参数, 传入两个整型值
	paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
	// 反射调用函数
	retList := funcValue.Call(paramList)
	// 获取第一个返回值, 取整数值
	fmt.Println(retList[0].Int())
}

反射三定律

官方提供了三条定律来说明反射:

  • 反射可将interface类型变量转换成反射对象;
  • 反射可将反射对象还原成interface对象;
  • 要修改反射对象,其值必须是可写的(反射其指针类型);
var x float64 = 3.4
v := reflect.ValueOf(x) // v is reflext.Value
var y float64 = v.Interface().(float64)
fmt.Println("value:", y) // 3.4

值类型不能直接修改,可通过传递地址并通过Elem获取后修改:

var x float64 = 3.4
v := reflect.ValueOf(&x)
v.Elem().SetFloat(7.8)
fmt.Println("x :", v.Elem().Interface())	// 7.8

interface

interface是Go实现抽象的一个非常强大的工具;当向接口赋值时,接口会存储实体的类型信息;反射就是通过接口的类型信息实现的。

interface类型是一种特殊类型,代表方法集合;可存放任何实现了其方法的值(实际存放的是(value,type)对)。reflect包中实现了反射的各种函数:

  • 提取interface的value的方法reflect.ValueOf()->reflect.Value
  • 提取interface的type的方法reflect.TypeOf()->reflect.Type

空interface类型(interface{})的方法集为空,所以可认为任何类型都实现了该接口;因此其可存放任何值。

底层结构

interface底层结构分为iface和eface描述接口,其区别是eface为不包含任何方法的空接口。

iface

iface定义如下:

  • tab 指向一个 itab 实体的指针:表示接口的类型(赋给此接口的实体类型);
  • data 指向接口具体的值:一般而言是一个指向堆内存的指针。
type iface struct {
	tab  *itab
	data unsafe.Pointer
}

itab结构:

  • _type 字段描述了实体的类型:包括内存对齐方式,大小等;
  • inter 字段则描述了接口的类型;
  • fun 字段放置是实体类中和接口方法对应(实体中其他方法不在此处)的方法地址,以实现接口调用方法的动态分派;一般在每次给接口赋值发生转换时会更新此表。
type itab struct {
	inter  *interfacetype
	_type  *_type
	link   *itab
	hash   uint32
	bad    bool
	inhash bool
	unused [2]byte
	fun    [1]uintptr
}

iface结构全貌图:

eface

eface结构:只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

到此这篇关于GoLang反射机制深入讲解的文章就介绍到这了,更多相关Go反射内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang学习之反射机制的用法详解

    目录 介绍 TypeOf() ValueOf() 获取接口变量信息 事先知道原有类型的时候 事先不知道原有类型的时候 介绍 反射的本质就是在程序运行的时候,获取对象的类型信息和内存结构,反射是把双刃剑,功能强大但可读性差,反射代码无法在编译阶段静态发现错误,反射的代码常常比正常代码效率低1~2个数量级,如果在关键位置使用反射会直接导致代码效率问题,所以,如非必要,不建议使用. 静态类型是指在编译的时候就能确定的类型(常见的变量声明类型都是静态类型):动态类型是指在运行的时候才能确定的类型(比如接

  • Golang 利用反射对结构体优雅排序的操作方法

    最近开始实习,工作技术栈主要Python和Golang,目前的任务把Python模块重构为GO模块,然后出现了一个问题,就是要将一个结构体按结构体中各个字段进行排序,然后写入Redis,对于Pyhon来说for循环就能解决,但是对于Go语言来说,每一次排序都要写一个比较函数,写出来的代码太丑,非常长,代码结构是一致,只是比较字段不一样而已,个人无法接受啊,网上搜索也没搜索到合适解决方法,所以自己想了一个解决方法来优雅排序. 比较函数: func reflectCmp(i, j interface

  • 深入理解Golang的反射reflect示例

    目录 编程语言中反射的概念 interface 和 反射 Golang的反射reflect reflect的基本功能TypeOf和ValueOf 说明 从relfect.Value中获取接口interface的信息 已知原有类型[进行“强制转换”] 说明 未知原有类型[遍历探测其Filed] 说明 通过reflect.Value设置实际变量的值 说明 通过reflect.ValueOf来进行方法的调用 说明 Golang的反射reflect性能 小结 总结 参考链接 编程语言中反射的概念 在计算

  • golang 如何用反射reflect操作结构体

    背景 需要遍历结构体的所有field 对于exported的field, 动态set这个field的value 对于unexported的field, 通过强行取址的方法来获取该值(tricky?) 思路 下面的代码实现了从一个strct ptr对一个包外结构体进行取值的操作,这种场合在笔者需要用到反射的场合中出现比较多 simpleStrtuctField 函数接受一个结构体指针,因为最后希望改变其值,所以传参必须是指针.然后解引用. 接下来遍历结构体的每个field, exported字段是

  • golang 如何通过反射创建新对象

    废话少说,直接贴代码~ type A struct { Name string } // 测试unit func TestReflect(t *testing.T) { reflectNew((*A)(nil)) } //反射创建新对象. func reflectNew(target interface{}) { if target == nil { fmt.Println("参数不能未空") return } t := reflect.TypeOf(target) if t.Kind

  • GoLang反射机制深入讲解

    目录 反射 反射类型Type 指针 结构体 反射值Value 结构体 空与有效性判断 修改值 函数调用 反射三定律 interface 底层结构 iface eface 反射 Go语言提供了reflect 包来访问程序的反射信息:定义了两个重要的类型Type和Value: reflect.TypeOf:获取任意值的类型对象(reflect.Type): reflect.ValueOf:获得值的反射值对象(reflect.Value): 反射类型Type Go语言程序中的类型(Type)指的是系统

  • PHP反射机制案例讲解

    简介 就算是类成员定义为private也可以在外部访问,不用创建类的实例也可以访问类的成员和方法. PHP自5.0版本以后添加了反射机制,它提供了一套强大的反射API,允许你在PHP运行环境中,访问和使用类.方法.属性.参数和注释等,其功能十分强大,经常用于高扩展的PHP框架,自动加载插件,自动生成文档,甚至可以用来扩展PHP语言.由于它是PHP內建的oop扩展,为语言本身自带的特性,所以不需要额外添加扩展或者配置就可以使用.更多内容见官方文档. 反射类型 PHP反射API会基于类,方法,属性,

  • Java反射机制的讲解

    Java中的反射提供了一种运行期获取对象元信息的手段.即正常方法是通过一个类创建对象,反射方法就是通过一个对象找到一个类的信息. Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method; 其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象.通过这四个对象我们可以粗略的看到一个类的各个组成部分. Java反射的作用: 在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属

  • Kotlin中的反射机制深入讲解

    前言 Java中的反射机制,使得我们可以在运行期获取Java类的字节码文件中的构造函数,成员变量,成员函数等信息.这一特性使得反射机制被常常用在框架中,想要比较系统的了解Kotlin中的反射,先从Java的反射说起. Java中的反射 通常我们写好的.java源码文件,经过javac的编译,最终生成了.class字节码文件.这些字节码文件是与平台无关的,使用时通过Classloader去加载这些.class字节码文件,从而让程序按照我们编写好的业务逻辑运行.Java的反射主要是从这些.class

  • Python反射机制实例讲解

    目录 1. 反射的四个函数 2. 类的反射操作 3. 当前模块的反射操作 4. 其他模块反射操作 5. 反射应用场景之一 6. 反射应用场景之二 7. 总结 通常,我们操作对象的属性或者方法时,是通过点"."操作符进行的.例如下面的代码: class Person: type = "mammal" def __init__(self, name): self.name = name def say_hi(self): print('Hello, my name is

  • Java反射机制的简单讲解

    🌱1. 什么是反射机制? 首先大家应该先了解两个概念,编译期和运行期,编译期就是编译器帮你把源代码翻译成机器能识别的代码,比如编译器把java代码编译成jvm识别的字节码文件,而运行期指的是将可执行文件交给操作系统去执行,JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制 简单说,反射机制值得是程序在运行时能够获取自身的信息.在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息. 🌱2. java反射机制提供了什么功能? 在运行时能够判断任意一个对象所属的类 在运行时构造任意一个类的对象 在运行时判断任意一个类所具有的成员变量和方法 在运行时调用任一对象的方法 在运行时创建新类对象 🌱3.new和反射创建有什么区别呢? ne

  • Java实例讲解反射机制是怎么一回事

    Java反射机制的概述 1.Java的反射(reflection) :机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法.这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制.反射被视为动态语言的关键,反射让Java成为一个准动态语言 .缺点增加不安全性. 2.动态语言(弱类型语言) 是运行时才确定数据类型的语言,变量在使用之前无需申明类型,通常变量的值是被赋值的那个值的类型..

  • Java深入分析讲解反射机制

    目录 反射的概述 获取Class对象的三种方式 通过反射机制获取类的属性 通过反射机制访问Java对象的属性 反射机制与属性配置文件的配合使用 资源绑定器 配合使用样例 通过反射机制获取类中方法 通过反射机制调用Java对象的方法 通过反射机制获取类中的构造方法 通过反射机制创建对象(调用构造方法) 通过反射机制获取一个类的父类和父接口 反射的概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的

  • GoLang并发机制探究goroutine原理详细讲解

    目录 1. 进程与线程 2. goroutine原理 3. 并发与并行 3.1 在1个逻辑处理器上运行Go程序 3.2 goroutine的停止与重新调度 3.3 在多个逻辑处理器上运行Go程序 通常程序会被编写为一个顺序执行并完成一个独立任务的代码.如果没有特别的需求,最好总是这样写代码,因为这种类型的程序通常很容易写,也很容易维护.不过也有一些情况下,并行执行多个任务会有更大的好处.一个例子是,Web 服务需要在各自独立的套接字(socket)上同时接收多个数据请求.每个套接字请求都是独立的

随机推荐