Bruce Blog Bruce Blog
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航

Bruce Tao

运维界的该溜子
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航
  • 基础

  • Go进阶

    • Go进阶
    • File操作
    • IO操作
    • 文件复制
    • 断点续传
    • bufio包
    • ioutil包
    • 遍历目录
    • 并发性Concurrency概念
    • Goroutine初识
    • Goroutine并发模型
    • Runtime包
    • 临界资源安全问题
    • sync包WaitGroup
    • 互斥锁
    • 读写锁
    • Channel通道
    • 关闭通道和通道上范围循环
    • 缓冲通道
    • 定向通道
    • time包中的通道相关函数
    • select语句
    • CSP并发模型
    • Go语言反射(一)
      • Go语言反射(二)
    • Go框架

    • Golang辅助

    • Golang
    • Go进阶
    Bruce
    2022-12-03
    目录

    Go语言反射(一)

    # 反射reflect

    • https://golang.google.cn/pkg/reflect/

    # 一、引入reflect

    官方Doc中Rob Pike给出的关于法神的定义

    Reflection in computing is the ability of a program to examine its own structure,particularly through tyes;it's a from of metaprogramming. It's also a great source of confusion.

    (在计算机领域,反射一种让程序---主要是通过类型---理解其自身结构的一种能力.它是元编程的组成之一,同时它也是一大引入困惑的难题.)

    # 维基百科

    在计算机科学中,反射是指计算机程序运行时(Run time)可以访问、检查和修改它本身状态或行为的一种能力.用比喻来说,反射就是程序字啊运行的时候能够"观察"并修改自身的行为.

    不同语言的反射模型不尽相同,有些语言还不支持反射.<<Go 语言圣经>>中是这样定义反射的:

    Go语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制

    # 为什么要用反射

    需要反射的2个场景:

    • 1.有时需要编写一个函数,但是并不知道传递的参数类型是什么,可能是没有约定好;也可能是传入的类型很多,这些类型并不能统一表示.这是反射就会用的上了.
    • 2.有时候需要根据某些条件决定调用哪个函数,比如根据用户自己的输入来决定.这时需要对函数和函数的参数进行反射,在运行期间动态地执行函数.

    但是对于反射,还是有几点不太建议使用的理由:

    • 1.与反射相关的代码,经常是难以阅读的.在软件工作中,代码可读性也是一个非常重要的指标.
    • 2.Go语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于发射代码是无能为力的.所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接panic,可能会造成严重的后果.
    • 3.反射性能影响还是比较大的,比正常代码运行速度慢一到两个数量级.所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性.

    反射是计算机语言提供的一个关键特性,掌握它,对编写通用(不要写死)的代码有比较大的帮助,另外,一些库或者框架提供的关键特性也是通用反射来实现.

    # 反射的概念

    这里说的是反射是指反射编程, 根据维基百科的定义,反射编程是指在程序运行期间,可以访问,检测和修改它本身状态或者行为的一种能力.用比喻来说,反射就是程序运行的时候能够"观察"并且修改自己的行为的一种能力.

    # 适用场景
    • 不能预先知道函数参数类型,或者参数类型有很多种,无法用同一个类型来表示
    • 函数需要根据入参来动态的执行不同的行为
    # 反射的优缺点
    # 反射的优点
    • 可以在一定程序上避免硬编码,提供灵活性和通用性
    • 可以作为一个第一个类对象发现并修改源代码的结构(如代码块,类,方法,协议等)
    # 反射的缺点
    • 由于将部分类型检查工作从编译期推迟到了运行时,使得一些隐藏的问题无法通过编译期发现,提高了代码出现bug的几率,搞不好就会panic
    • 反射出变量的类型需要额外的开销,降低了代码的运行效率
    • 反射的概念和语法比较抽象,过多的使用反射,使得代码难以被其他人读懂,不利于合作与交流
    # Golang反射的基本原理
    • Golang是怎么实现在程序运行的时候能够"观察"并且修改自己的行为的能力的呢

    Golang反射是通过接口来实现的,通过隐式转换,普通的类型被转换成interface类型,这个过程涉及到类型转换的过程,首先从Golang类型转为interface类型, 再从interface类型转换成反射类型, 再从反射类型得到想的类型和值的信息.

    总的基本流程见下面的图.

    image-20221202234438739

    在Golang obj转成interface这个过程中, 分2种类型

    • 包含方法的interface, 由runtime.iface实现
    • 不包含方法的interface, 由runtime.eface实现

    这2个类型都是包含2个指针, 一个是类型指针, 一个是数据指针, 这2个指针是完成反射的基础.

    实质上, 通过上述转换后得到的2种interface, 已经可以实现反射的能力了. 但作为语言本身, 标准库将这个工作封装好了, 就是 reflect.Type与reflect.Value , 方便使用反射.

    reflect. TypeOf 和 reflect.ValueOf 是一个转换器, 完成反射的的最终转换, 得到 reflect.Type, reflect.Value 对象, 得到这2个对象后, 就可以完成反射的准备工作了, 通过 reflect.Type, reflect.Value 这对类型, 可以实现反射的能力.

    上图中, 最后一根线说的是由reflect. Value变成普通inteface的过程, 然后通过具体的类型断言, 转成真正的类型. 可能有人会觉得奇怪, 为什么 reflect.Value 可以转成interface对象, reflect. Type 不行.

    # Golang反射提供的能力
    • 运行时获取对象的类型, 值
    • 创建对象, 执行方法
    • 反射对象转换成Go语言对象
    • 动态修改对象的值
    # Go反射三定律

    如同物理反射定律一样, 反射编程中也有反射定律. 这个反射定律是在go语言官方博客中, The Laws of Reflection (opens new window) .简单概括一下就是下面三点:

    • Golang对象可以转换成反射对象
    • 反射对象可以转换成Golang对象
    • 可寻址的reflect对象可以更新值
    # Golang中反射的实际运用

    反射在标准库中和第三方库中有着大量的运用,如下简单示例:

    • encoding/json marshal方法 (opens new window)
    • fmt. Printf
    • 各种orm工具

    json序列化的例子

    func (e *encodeState) marshal(v interface{}, opts encOpts) (err error) {
    	defer func() {
    		if r := recover(); r != nil {
    			if je, ok := r.(jsonError); ok {
    				err = je.error
    			} else {
    				panic(r)
    			}
    		}
    	}()
    	e.reflectValue(reflect.ValueOf(v), opts)
    	return nil
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    这里将interface v通过 reflect.ValueOf 转换成 reflect.Value 对象进一步做下处理.

    func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
    	valueEncoder(v)(e, v, opts)
    }
    
    func valueEncoder(v reflect.Value) encoderFunc {
    	if !v.IsValid() {
    		return invalidValueEncoder
    	}
    	return typeEncoder(v.Type())
    }
    
    func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
    	// If we have a non-pointer value whose type implements
    	// Marshaler with a value receiver, then we're better off taking
    	// the address of the value - otherwise we end up with an
    	// allocation as we cast the value to an interface.
    	if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(marshalerType) {
    		return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
    	}
    	if t.Implements(marshalerType) {
    		return marshalerEncoder
    	}
    	if t.Kind() != reflect.Ptr && allowAddr && reflect.PtrTo(t).Implements(textMarshalerType) {
    		return newCondAddrEncoder(addrTextMarshalerEncoder, newTypeEncoder(t, false))
    	}
    	if t.Implements(textMarshalerType) {
    		return textMarshalerEncoder
    	}
    
    	switch t.Kind() {
    	case reflect.Bool:
    		return boolEncoder
    	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    		return intEncoder
    	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
    		return uintEncoder
    	case reflect.Float32:
    		return float32Encoder
    	case reflect.Float64:
    		return float64Encoder
    	case reflect.String:
    		return stringEncoder
    	case reflect.Interface:
    		return interfaceEncoder
    	case reflect.Struct:
    		return newStructEncoder(t)
    	case reflect.Map:
    		return newMapEncoder(t)
    	case reflect.Slice:
    		return newSliceEncoder(t)
    	case reflect.Array:
    		return newArrayEncoder(t)
    	case reflect.Ptr:
    		return newPtrEncoder(t)
    	default:
    		return unsupportedTypeEncoder
    	}
    }
    
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58

    这里通过 reflect.Type 类型, 来初始化不同的encoder, 大量运用了反射, 实现了序列化, 在不支持反射的语言如c++, 实现对象json序列化, 就比较麻烦.

    # 总结

    Golang反射严重依赖于 interface{} 这个万能的 容器 类型, 这个 interface{} 类型相当于java中的class类型, 是实现反射的桥梁. 我们在谈Golang反射时, 主要还是围绕 interface{} 展开来说的.

    反射是现代静态语言的通用底层技术, 能在一定程序上提升静态类型的灵活性.

    # 二、相关基础

    反射是和实现的?Go中的interface,它是Go语言实现抽象的一个非常强大的工具.当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上.

    Go语言在reflect包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值.在就进行更加详细的了解之前,需要重新回顾Go语言相关的一些特性,从这些特性中了解其反射机制是如何使用的.

    特点 说明
    go语言是静态类型语言. 编译时类型已经确定,比如对已基本数据类型的再定义后的类型,反射时候需要确认返回的是何种类型.
    空接口interface{} go的反射机制是要通过接口来进行的,而类似于Java的Object的空接口可以和任何类型进行交互,因此对基本数据类型等的反射也直接利用了这一特点.

    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.

    Go是静态类型语言,每个变量都拥有一个静态类型,这意味着每个变量类型在编译时都是确定的: int,float32,*AutoType,[]byte,chan []int诸如此类.

    在反射的概念中,编译时就知道变量类型的是静态类型;运行时才知道一个变量类型的叫做动态类型.

    • 静态类型

      静态类型就是变量声明时的赋予的类型.比如:

    type MyInt int //就是静态类型
    
    type A struct {
        Name string //string就是静态
    }
    var i *int // *int就是静态类型
    
    1
    2
    3
    4
    5
    6
    • 动态类型

      动态类型: 运行时给这个变量赋值时,这个值的类型(如果值为nil的时候没有动态类型).一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量是接口类型).

      var A interface{} //静态类型interface{}
      A = 10 //静态类型为interface{} 动态为int
      A = "String" //静态类型为interface{}  动态为string
      var M *int 
      A = M   // A的值可以改变
      
      1
      2
      3
      4
      5

    Go语言的反射就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是static type),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concrete type),只有interface类型才有发射一说.

    在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:

    (value,type)
    
    1

    value是实际变量值,type是实际变量的类型,一个interface{}类型的变量包含了2个指针,一个指针指向值的类型[对应concrete type],另一个指针指向实际的值[对应value].

    例如,创建类型为*os.File的变量,然后将其赋给一个接口变量r:

    tty, err := os.OpenFile("/dev/tty",os.O_RDWR,0)
    
    var r io.Reader
    r = tty
    
    1
    2
    3
    4

    接口变量r的pair中将记录如下信息: (tty, *os.File),这个pair在接口变量的连接赋值过程中是不变的,将接口变量r赋给另一个接口变量w:

    var w io.Writer
    w = r.(io.Writer)
    
    1
    2

    接口变量w的pair与r的pair相同,都是: (tty, *os.File),即使w是空接口类型,pair也不是不变的.

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

    所以要理解两个基本概念Type和Value,它们也是Go语言中reflect空间里最重要的两个类型.

    # 三、反射的规则

    其实反射的操作步骤非常简单,就是通过实体对象获取反射对象(Value、Type),然后操作相应的方法即可.

    下面描述了实例、Value、Type三者之间的转换关系:

    image-20221125224738063

    # 反射API的分类:
    • 1.从实体到Value

      通过实例获取Value对象,直接使用reflect.ValueOf()函数.例如:

      func ValueOf(i interface {}) Value
      
      1
    • 2.从实体到Type

      通过实例获取反射对象的Type,直接使用reflect.TypeOf()函数.例如:

      func TypeOf(i interface{}) Type
      
      1
    • 3.从Type到Value

      Type里面只有类型信息,所以直接从一个Type接口变量里面是无法获得实例的Value的,但可以通过该Type构建一个新实例的Value.reflect包提供了两种方法,示例如下:

      // New 返回的是一个`Value`,该`Value`的`type`为`PtrTo(typ)`,即`Value`是指定`typ`的指针类型
      func New(typ Type) Value
      
      //Zero 返回的是一个 `typ`类型的零值,注意返回的`Value`不能寻址,位不可改变
      func Zero(typ Type) Value
      
      1
      2
      3
      4
      5

      如果知道一个类型值的底层存放地址,则还有一个函数是可以依据type和该地址值恢复出Value的.例如:

      func NewAt(typ Type,p unsafe.Pointer) Value
      
      1
    • 4.从Value到Type

      从反射对象Value到Type可以直接调用Value的方法,因为Value内部存放这到Type类型的指针.例如:

      func(v Value) Type() Type
      
      1
    • 5.从Value到实例

      Value本身就包含类型和值信息,reflect提供了丰富的方法来实现从Value到实例的转换.例如:

      //该方法最通用,用来将`Value`转换为空接口,该空接口内部存放具体类型实例
      //可以使用接口类型查询去还原为具体的类型
      
      //`Value`自身也提供丰富的方法,直接将`Value`转换为简单类型实例,如果类型不匹配,则直接引起`panic`
      func (v Value) Bool () bool
      func (v Value) Float() float64
      func (v Value) Int() int64
      func (v Value) Uint() uint64
      
      1
      2
      3
      4
      5
      6
      7
      8
    • 6.从Value的指针到值

      从一个指针类型的Value获得值类型Value有两种方法,示例如下.

      //如果 v 类型是接口,则`Elcm()`返回接口绑定的实例的`Value`,如采 v 类型是指针,则返回指针值的`Value`,否则引起panic
      func (v Value) Elem() Value
      
      //如果 v 是指针,则返回指针值的`Value`,否则返回 v 自身,该函数不会引起 panic
      func Indirect(v Value) Value
      
      1
      2
      3
      4
      5
    • 7.Type指针和值的相互转换

      指针类型Type到值类型Type.例如:

      //t 必须是 `Array、Chan、Map、Ptr、Slice`,否则会引起 panic
      //Elem 返回的是其内部元素的 Type
      t.Elem() Type
      
      1
      2
      3

      值类型Type到指针类型Type.例如:

      //PtrTo 返回的是指向 t 的指针型 Type
      func PtrTo(t Type) Type
      
      1
      2
    • 8.Value值的可修改性

      Value值的修改涉及如下两个方法:

      //通过 CanSet 判断是否能修改
      func (v value) CanSet() bool
      
      //通过 Set 进行修改
      func (v Value) Set(x Value)
      
      1
      2
      3
      4
      5

    Value值是在什么情况下可以修改? 在知道示例对象传递给接口的是一个完全的值考本,如果调用反射的方法reflect.ValueOf()传进去的是一个值类型变量,则获得的Value实际上是原对象的一个副本,这个Value是无论如何也不能被修改的.

    根据Go官方关于反射的博客,反射有三大定律:

    1.Reflection goes from interface value to reflection object

    2.Reflection goes from reflection object to interface value.

    3.To modify a reflection object,the value must be settable.

    • 第一条是最基本的: 反射可以从接口值得发哦反射对象

      反射是一种检测存储在interface中的类型和值机制.这可以通过TypeOf函数和ValueOf函数得到.

    • 第二条实际上和第一条是相反的机制,反射可以从反射对象获得接口值.

      它将ValueOf的返回值通过interface{}函数反向转变成interface变量.

    前两条就是说接口型变量和反射类型对象可以相互转化,反射类型对象实际上就是指的前面说的reflect.Type和reflect.Value

    • 第三条不太好懂: 如果需要操作一个反射变量,则其值必须可以修改.

      反射变量可设置的本质是它存储了原变量本身,这样反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑.所以第二种情况在语言层面是不被允许的.

    # 四、反射的使用

    • 一般用到的包是reflect
    # 3.1.reflect的基本功能TypeOf和ValueOf

    既然反射就是用来检测存储在接口变量内部(值value;类型concrete type)pair的对的一种机制.那么在Golang的reflect反射包中有什么样的方式可以让直接获取到变量内部的信息呢?它提供了两种类型(或者说两个方法)可以很容易的访问接口变量内容,分别是reflect.ValueOf()和reflect.TypeOf(),官方的解释.

    //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 {...}
    
    翻译: `ValueOf`用来获取输入参数接口中的数据的值,如果接口为空则返回`0`
    
    //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 {...}
    
    翻译: `typeOf`用来动态获取输入参数接口中的值的类型,如果接口为空则返回`nil`
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    reflect.TypeOf()是获取pair中的type,reflect.ValueOf()获取pair中的value.

    使用reflect一般分成三步:

    首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)

    t := reflect.TypeOf(i) //得到类型的元数据,通过`t`能获取类型定义里面的所有元素
    v := reflect.ValueOf(i) //得到实际的值,通过`v`获得存储在里面的值,还可以去改变值
    
    1
    2

    示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	/*
    		反射操作: 通过反射,可以获取一个接口类型变量的 类型和数值
    	*/
    
    	var x float64 = 3.4
    	fmt.Println("type: ", reflect.TypeOf(x))
    	fmt.Println("value: ", reflect.ValueOf(x))
    
    	fmt.Println("-----------------------------")
    	//根据反射的值,来获取对应的类型和数值
    	v := reflect.ValueOf(x)
    	fmt.Println("kind is float64: ", v.Kind() == reflect.Float64)
    	fmt.Println("type: ", v.Type())
    	fmt.Println("value: ", v.Float())
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    执行结果

    image-20221125164358391

    # 3.2.从reflect.Value中获取接口interface的信息

    当执行reflect.ValueOf(interface)之后,就得到了一个类型为reflect.Value变量.可以通过本身的Iterface()方法获得接口变量的真是内容,然后可以通过类型判断进行转换,转换为原有真是类型.不过,可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明:

    # 已知原有类型

    已知类型后转换为其对应的类型的做法如下,直接通过Iterface方法然后强制转换,如下:

    realValue := value.Interface().(已知的类型)
    
    1

    示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	var num float64 = 1.23
    
    	//"接口类型变量" ---> "反射类型对象"
    	value := reflect.ValueOf(num)
    
    	//"反射类型对象" ---> "接口类型变量"
    	convertValue := value.Interface().(float64)
    	fmt.Println(convertValue)
    
    	/*
    		反射类型对象 ---> 接口类型变量,理解为"强制转换"
    		Golang对类型要求非常严格,类型一定要完全符合
    		一个是*float64,一个float64,如果弄混,直接panic
    	*/
    	pointer := reflect.ValueOf(&num)
    	convertPointer := pointer.Interface().(*float64)
    	fmt.Println(convertPointer)
    }
    
    
    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

    运行结果

    1.23
    0xc0000ba058
    
    1
    2

    说明:

    • 1.转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
    • 2.转换的时候,要区分是指针还是值
    • 3.也就是说反射可以将"反射类型对象"再重新转换为"接口类型变量"
    # 未知原有类型

    很多情况下,可能并不知道其具体类型,那么这个时候,该如何做呢?需要进行遍历探测器Filed来得知,示例如下:

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    
    
    
    type Person struct {
    	Name string
    	Age int
    	Sex string
    }
    
    func (p Person)Say(msg string)  {
    	fmt.Println("hello,",msg)
    }
    func (p Person)PrintInfo()  {
    	fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex)
    }
    
    
    
    func main() {
    	p1 := Person{"王二狗",30,"男"}
    
    	DoFiledAndMethod(p1)
    
    }
    
    // 通过接口来获取任意参数
    func DoFiledAndMethod(input interface{}) {
    
    	getType := reflect.TypeOf(input) //先获取input的类型
    	fmt.Println("get Type is :", getType.Name()) // Person
    	fmt.Println("get Kind is : ", getType.Kind()) // struct
    
    	getValue := reflect.ValueOf(input)
    	fmt.Println("get all Fields is:", getValue) //{王二狗 30 男}
    
    	// 获取方法字段
    	// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
    	// 2. 再通过reflect.Type的Field获取其Field
    	// 3. 最后通过Field的Interface()得到对应的value
    	for i := 0; i < getType.NumField(); i++ {
    		field := getType.Field(i)
    		value := getValue.Field(i).Interface() //获取第i个值
    		fmt.Printf("字段名称:%s, 字段类型:%s, 字段数值:%v \n", field.Name, field.Type, value)
    	}
    
    	// 通过反射,操作方法
    	// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
    	// 2. 再公国reflect.Type的Method获取其Method
    	for i := 0; i < getType.NumMethod(); i++ {
    		method := getType.Method(i)
    		fmt.Printf("方法名称:%s, 方法类型:%v \n", method.Name, method.Type)
    	}
    }
    
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60

    执行结果

    image-20221125235410739

    image-20221126000458016

    说明

    通过运行结果可以得知获取未知类型的interface的具体变量及其类型的步骤为:

    • 1.想获取interface的reflect.Type,然后通过NumField进行遍历
    • 2.在通过reflect.Type的Field获取其Field
    • 3.最后通过Field的Interface()得到对应的value

    通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:

    • 1.先获取interface的reflect.Type,然后通过NumMethod进行遍历
    • 2.再分别通过reflect.Type的Method获取对应的真是的方法(函数)
    • 最后对结果取其Name和Type得知具体的方法名
    • 也就是说反射可以将"反射类型对象"再重新转换为"接口类型变量"
    • struct或者struct的嵌套都是一样的判断处理方式.

    kind有slice,map,pointer指针,struct,interface,string,Array,Function,int或其他基本类型组成.kind和Type之前要做好区分.如果定义一个type Person struct{},那么Kind就是struct,Type就是Person.

    反射变量对应的Kind方法的返回值是基本类型,并不是静态类型.

    type myint int
    var x myint = 100
    v := reflect.ValueOf(X)
    
    1
    2
    3

    变量v的Kind依旧是reflect.int,而不是myint这个静态类型.Type可以表示静态类型,而Kind不可以.

    # 3.3.通过reflect.Value设置实际变量的值

    reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflect.Value修改实际变量X的值,即: 要修改反射类型的对象就一定要保证其值是addressable的.

    也就是说: 要想修改一个变量的值,那么必须通过该变量的指针地址,取消指针的引用.通过refPtrVal:= reflect.ValueOf(&var)的方式获取指针类型,使用refPtrVal.elem().set (一个新的reflect.Value)来进行更改,传递给set()的值也必须是一个reflect.value.

    这里需要一个方法:

    image-20221126133939946

    ​

    解释起来: Elem返回接口v包含的值或指针v指向的值.如果v的类型不是interface或ptr,它会恐慌.如果v为零,则返回零值.

    如果变量是一个指针、map、slice、channel、Array.那么可以使用reflect.TypeOf(v).Elem()来确定包含的类型.

    示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	var num float64 = 1.23
    	fmt.Println("num的数值是: ", num)
    
    	//需要操作指针
    	//通过reflect.ValueOf()  获取num的Value对象
    	pointer := reflect.ValueOf(&num)
    	newValue := pointer.Elem()
    
    	fmt.Println("类型: ", newValue.Type())       //float64
    	fmt.Println("是否修改数据: ", newValue.CanSet()) //true
    
    	//重新赋值
    	newValue.SetFloat(3.14)
    	fmt.Println(num)
    
    	//如果reflect.ValueOf地参数不是指针
    	value := reflect.ValueOf(num)
    	//value.SetFloat(6.28) // panic: reflect: reflect.Value.SetFloat using unaddressable value
    	fmt.Println(value.CanSet())
    
    	//value.Elem() //如果非指针,会直接panic: reflect: call of reflect.Value.Elem on float64 Value
    }
    
    
    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

    执行结果

    num的数值是:  1.23
    类型:  float64     
    是否修改数据:  true
    3.14               
    false  
    
    1
    2
    3
    4
    5

    说明

    • 1.需要传入的参数*float64这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针.

    • 2.如果传入的参数不是指针,而是变量,那么

      • 通过Elem获取原始值对应的对象则直接panic
      • 通过CanSet方法查询是否可以设置返回false
    • 3.newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可以修改,否则不能修改,修改完之后再进行打印发现真的已经修改了.

    • 4.reflect.Value.Elem()表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的

      5.也就是说如果要修改反射类型对象,其值必须是addressable[对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象]

    • 6.struct或者struct的嵌套都是一样的判断处理方式.

    尝试修改结构体中的字段数值

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Student struct {
    	Name   string
    	Age    int
    	School string
    }
    
    func main() {
    	s1 := Student{"王二狗", 19, "清华大学"}
    	//通过反射,更改对象的数值: 前提也是数据可以被更改
    	fmt.Printf("%T\n", s1)
    	p1 := &s1
    	fmt.Printf("%T\n", p1)
    	fmt.Println(s1.Name)
    	fmt.Println((*p1).Name, p1.Name)
    
    	//修改数值
    	value := reflect.ValueOf(&s1)
    	if value.Kind() == reflect.Ptr {
    		newValue := value.Elem()
    		fmt.Println(newValue.CanSet()) //true
    
    		f1 := newValue.FieldByName("Name")
    		f1.SetString("布鲁斯")
    		f2 := newValue.FieldByName("School")
    		f2.SetString("北京大学")
    		fmt.Println(s1)
    	}
    }
    
    
    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
    35
    36

    执行结果

    main.Student 
    *main.Student
    王二狗              
    王二狗 王二狗       
    true                
    {布鲁斯 19 北京大学}
    
    1
    2
    3
    4
    5
    6

    练习

    • 对silce、map、channel中的数据进行修改
    # 3.4.通过reflect.Value来进行方法的调用

    这算是一个高级用法了,前面只是介绍了对类型、变量的几种反射的用法,包括如何获取其值、其类型、以及如何重新设置新值.但是在项目应用中,另外一个常用并且属于高级的用法,就是通过reflect来进行方法[函数]的调用.比如要做架构工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么通过什么手段来扩展让用户能够自定义呢?关键点在于用于的自定义方法是未可知的,因为可以通过reflect来搞定.

    Call()方法:

    image-20221126184852525

    通过反射,调用方法

    先获取结构体对象,然后

    示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person1 struct {
    	Name string
    	Age  int
    	Sex  string
    }
    
    //有参数的方法
    func (p Person1) Say1(msg string) {
    	fmt.Println("hello", msg)
    }
    
    //无参数的方法
    func (p Person1) PrintInfo1() {
    	fmt.Printf("姓名: %s,年龄: %d,性别: %s\n", p.Name, p.Age, p.Sex)
    }
    
    func (p Person1) Test(i, j int, s string) {
    	fmt.Println(i, j, s)
    }
    
    func main() {
    	/*
    		通过反射来进行方法的调用
    		思路:
    		step1: 接口变零 ---> 对象反射对象: Value
    		step2: 获取对应的方法对象:
    		step3: 将方法对象进行调用: Call()
    
    	*/
    	p1 := Person1{"Bruce", 20, "男"}
    	value := reflect.ValueOf(p1)
    	fmt.Printf("kind: %s,type: %s\n", value.Kind(), value.Type()) //kind: func,type: func()
    
    	methodValue := value.MethodByName("PrintInfo1")
    	fmt.Printf("kind: %s,type: %s\n", methodValue.Kind(), methodValue.Type()) // kind: struct,type: main.Person1
    
    	//没有参数,进行调用
    	methodValue.Call(nil) //没有参数传nil
    
    	args1 := make([]reflect.Value, 0) //或者是创建一个空的切片也可以
    	methodValue.Call(args1)
    
    	methodValue1 := value.MethodByName("Say1")
    	fmt.Printf("kind: %s,type: %s\n", methodValue1.Kind(), methodValue1.Type()) // kind: func,type: func(string)
    	args2 := []reflect.Value{reflect.ValueOf("反射机制")}
    	methodValue1.Call(args2)
    
    	methodValue2 := value.MethodByName("Test")
    	fmt.Printf("kind: %s,type: %s\n", methodValue2.Kind(), methodValue2.Type())
    
    	args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200), reflect.ValueOf("hello world")}
    	methodValue2.Call(args3)
    
    }
    
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

    运行结果

    kind: struct,type: main.Person1
    kind: func,type: func()
    姓名: Bruce,年龄: 20,性别: 男
    姓名: Bruce,年龄: 20,性别: 男
    kind: func,type: func(string)
    hello 反射机制
    kind: func,type: func(int, int, string)
    100 200 hello world
    
    1
    2
    3
    4
    5
    6
    7
    8
    # 通过反射,调用函数

    首选要确认一点,函数像普通的变量一样,是可以把函数作为一种变量类型的,而且是引用类型.如果说Fun()是一个函数,那么f1 := Fun也是可以的,那么f1也是一个函数,如果直接调用f1(),那么运行的就是Fun()函数.

    那么就先通过ValueOf()来获取函数的反射对象,可以判断它的Kind,是一个func,那么就可以执行Call()进行含糊的调用.

    示例代码

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    )
    
    func main() {
    	//函数的反射
    	/*
    		思路: 函数也是看做接口变量类型
    		step1: 函数 ---> 反射对象,Value
    		step2: kind ---> func
    		step3: call()
    	*/
    	f1 := fun1
    	value := reflect.ValueOf(f1)
    	fmt.Printf("kind: %s,type: %s\n", value.Kind(), value.Type())
    
    	value2 := reflect.ValueOf(fun2)
    
    	value3 := reflect.ValueOf(fun3)
    	fmt.Printf("kind: %s,type: %s\n", value2.Kind(), value2.Type())
    	fmt.Printf("kind: %s,type: %s\n", value3.Kind(), value3.Type())
    
    	//通过反射调用函数
    	value.Call(nil)
    	value2.Call([]reflect.Value{reflect.ValueOf(1000), reflect.ValueOf("bruce")})
    	resultValue := value3.Call([]reflect.Value{reflect.ValueOf(2000), reflect.ValueOf("班纳")})
    	fmt.Printf("%T\n", resultValue) //[]reflect.Value
    	fmt.Println(len(resultValue))
    	fmt.Printf("kind: %s,type: %s\n", resultValue[0].Kind(), resultValue[0].Type()) //kind: string,type: string
    
    	s := resultValue[0].Interface().(string)
    	fmt.Println(s)
    	fmt.Printf("%T\n", s)
    }
    
    func fun1() {
    	fmt.Println("我是函数fun1(),无参的...")
    }
    
    func fun2(i int, s string) {
    	fmt.Println("我是函数fun2(),有参的...", i, s)
    }
    
    func fun3(i int, s string) string {
    	fmt.Println("我是函数fun3(),有参的,也有返回值...", i, s)
    	return s + strconv.Itoa(i)
    }
    
    
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    执行结果

    kind: func,type: func(int, string)
    kind: func,type: func(int, string) string
    我是函数fun1(),无参的...
    我是函数fun2(),有参的... 1000 bruce
    我是函数fun3(),有参的,也有返回值... 2000 班纳
    []reflect.Value
    1
    kind: string,type: string
    班纳2000
    string
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    说明

    • 1.要通过反射来调用起对应的方法,必须要先通过reflect.ValueOf(interface)来湖区到reflect.Value,得到"反射类型对象"后才能做下一步处理
    • 2.reflect.Value.MethodByName这里MethodByName,需要指定准确真实的方法名字,如果错误将直接panic,MethodByName返回一个函数值对应的reflect.Value方法的名字.
    • 3.[]reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定.
    • 4.reflect.Value的Call这个方法,这个方法最终调用真实的方法,参数务必保持一致,如果reflect.Value.Kind不是一个方法,那么将直接panic
    • 5.本来可以用对象访问方法直接调用的,但是如果要通过反射,那么首先要将方法注册,也及时methodByName,然后通过反射调用methodValue.Call
    上次更新: 2024/04/09, 16:48:42
    CSP并发模型
    Go语言反射(二)

    ← CSP并发模型 Go语言反射(二)→

    最近更新
    01
    AWS NAT-NetWork-Firwalld配置(一)
    04-09
    02
    AWS NAT-NetWork-Firwalld配置(二)
    04-09
    03
    kubernetes部署minio对象存储
    01-18
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Bruce Tao Blog Space | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式