Go语言备忘录(2):反射的原理与使用详解
本文内容是本人对Go语言的反射原理与使用的备忘录,记录了关键的相关知识点,以供翻查。 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录(2):反射的原理与使用详解,多谢! 参考书籍《The Go Programming Language》、《Go In Action》、《Go语言学习笔记》等
目录:
- Go的变量都是静态类型(声明时指定的类型),它也有底层类型(定义类型时指定的基础类型,即:它是以什么形式存储的);
- 一个接口变量存储了一对(value, type):赋值给这个接口变量的具体值value、以及这个值的类型描述符type;
- Go的接口变量都是静态类型化的:一个接口类型变量总是保持同一个静态类型(即声明时指定的接口类型),即使在运行时它保存的值的类型发生变化,这些值总是满足这个接口。
- 接口的静态类型决定了能用接口变量调用哪些方法(接口中定义的方法,它们是保存的值的方法集的子集);
- 反射是一种检查存储在接口变量中的(value, type)对的机制,反射操作所需的全部信息都源自接口变量(通过把变量转换为空接口变量,从而获得了该变量的value、type,这样就可以进行一系列的“反射操作”);
- reflect包中的两个类型:Type和Value,这两种类型提供了访问一个接口变量中所包含的(value, type)对的途径;
2.反射由reflect包提供支持,主要方法:
- func TypeOf ( i interface{} ) Type:
如 reflect.Typeof(x) ,形参x被保存为一个接口值并作为参数传递(复制),方法内部会把该接口值拆包恢复出x的类型信息保存为reflect.Type并返回; - func ValueOf ( i interface{} ) Value:
如 reflect.ValueOf(x) ,形参被保存为一个接口值并作为参数传递(复制), 方法内部把该接口值的值恢复出来保存为reflect.Value并返回;
- Type 接口:可以表示一个Go类型
- Kind() 将返回一个常量,表示具体类型的底层类型
- Elem()方法返回指针、数组、切片、map、通道的基类型;
- 可用反射提取struct tag,还能自动分解,常用于ORM映射、数据验证等;
- 辅助判断方法Implements()、ConvertibleTo()、AssignableTo()
- Value 结构体:可以持有一个任意类型的值
- 调用 Value 的 Type() 将返回具体类型所对应的 reflect.Type(静态类型)
- 调用 Value 的 Kind() 将返回一个常量,表示具体类型的底层类型
- Interface方法是ValueOf方法的逆,它把一个reflect.Value恢复成一个接口值:把Value中保存的类型和值的信息打包成一个接口表示并返回;如:
y,ok := v.Interface().(float64) // y 的类型被断言为 float64
fmt.Println(y)
以上可简写为这样:
fmt.Println(v.Interface()) //fmt.Println会把它恢复出来 - 通道类型的反射对象:有TrySend()、TryRecv()方法;
- IsNil()方法判断反射对象保存的值是否为nil;
- v := reflect.ValueOf(&x)
- m := v.MethodByName("Show")
- in := []reflect.Value{
- reflect.ValueOf(23),
- reflect.ValueOf(323),
- }
- out := m.Call(in) //对于变参可用CallSlice方法
- 原理:
- 因为给Go的函数、方法传递的都是形参的副本,同样的,反射一个对象时,形参被保存为一个接口对象并作为参数传递(复制),该接口变量是non-settable的,返回的Value也是non-settable的,对它调用Set方法会出现错误;
- Value的CanSet方法用于测试一个Value的Settablity性质,它有点像unaddressability,但是更加严格,描述的是一个反射对象能够修改创造它的那个实际存储的值的能力。settability由反射对象是否保存原始项而决定。
- 所以,如果想通过反射来修改对象,必须先把该对象的指针传给reflect.ValueOf(&x),这样得到的Value对象内部就保存了原对象指针的副本,只有找到该指针指向的值才能修改原始对象,通过Elem()方法就可以获得一个保存了原对象的Value对象,此时的Value对象就是settable的;
- d.CanAddr()方法:判断它是否可被取地址
- d.CanSet()方法:判断它是否可被取地址并可被修改
- 方式1:通过把反射对象转换回原对象类型的指针,然后直接修改该指针
- px := d.Addr().Interface().(*int)
- 第一步是调用Addr()方法,它返回一个Value,里面保存了指向变量的指针。
- 然后是在Value上调用Interface()方法,也就是返回一个interface{},里面通用包含指向变量的指针。
- 最后,如果我们知道变量的类型,我们可以使用类型的断言机制将得到的interface{}类型的接口强制环为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了
- 方式2:可直接通过Set()方法来修改
- d.Set(reflect.ValueOf(4))
- SetInt、SetUint、SetString和SetFloat等方法:d.SetInt(3),注意:虽然如SetInt()等方法只要参数变量的底层数据类型是有符号整数就可以工作,但不能是一个引用interface{}类型的reflect.Value
- 小结:Value反射对象为了修改它们所表示的东西必须要有这些东西的地址
- 例子:
- var x float64 = 3.4
- p := reflect.ValueOf(&x) // 注意这里:把x地址传进去了!
- fmt.Println(p.Type()) //*float64
- fmt.Println(p.CanSet()) //false 这里的p只是指针,仍然是non-settable的
- v := p.Elem() //此时的v保存了x
- fmt.Println( v.CanSet()) //true
- v.SetFloat(7.1)
- fmt.Println(v.Interface()) //7.1
- fmt.Println(x) //7.1
虽然反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,但不能修改这些未导出的成员。因为一个struct中只有被导出的字段才是settable的。
- type T struct {
- A int
- B string
- }
- t := T{23, "skidoo"}
- s := reflect.ValueOf(&t).Elem()
- typeOfT := s.Type()//把s.Type()返回的Type对象复制给typeofT,typeofT也是一个反射。
- for i := 0; i < s.NumField(); i++ {
- f := s.Field(i)//迭代s的各个域,注意每个域仍然是反射。
- fmt.Printf("%d: %s %s = %v\n", i,
- typeOfT.Field(i).Name, f.Type(), f.Interface())//提取了每个域的名字
- }
- //0: A int = 23
- //1: B string = skidoo
- s.Field(0).SetInt(77) //s.Field(0).Set(reflect.ValueOf(77))
- s.Field(1).SetString("Sunset Strip")
- fmt.Println("t is now", t) //t is now {77 Sunset Strip}
- 定义一个可适应不同数据类型的通用模板算法函数,然后用reflect.MakeFunc()方法,可以把任意函数类型变量绑定到通用模板算法函数(为一系列函数对象指定同一个函数体);
- package main
- import (
- "reflect"
- "strings"
- "fmt"
- )
- //通用算法函数体模板
- func add(args []reflect.Value) (results []reflect.Value) {
- if len(args) == 0 {
- return nil
- }
- var r reflect.Value
- switch args[0].Kind() {
- case reflect.Int:
- n:=0
- for _,a:=range args{
- n+=int(a.Int())
- }
- r = reflect.ValueOf(n)
- case reflect.String:
- ss := make([]string,0,len(args))
- for _,s:=range args{
- ss = append(ss,s.String())
- }
- r=reflect.ValueOf(strings.Join(ss,""))
- }
- results = append(results,r)
- return
- }
- func makeAdd(T interface{}) {
- fn:=reflect.ValueOf(T).Elem()
- v:=reflect.MakeFunc(fn.Type(),add) //把原始函数变量的类型和通用算法函数存到同一个Value中
- fn.Set(v) //把原始函数指针变量指向v,这样它就获得了函数体
- }
- func main() {
- //定义函数变量,未定义函数体
- var intAdd func(x,y int) int
- var strAdd func(a,b string) string
- makeAdd(&intAdd)
- makeAdd(&strAdd)
- fmt.Println(intAdd(12,23)) //35
- fmt.Println(strAdd("hello, ","world!")) //hello, world!
- }
Go语言备忘录(2):反射的原理与使用详解的更多相关文章
- Go语言备忘录:反射的原理与使用详解
目录: 预备知识 reflect.Typeof.reflect.ValueOf Value.Type 动态调用 通过反射可以修改原对象 实现类似“泛型”的功能 1.预备知识: Go的变量都是静态类 ...
- Java基础-反射(reflect)技术详解
Java基础-反射(reflect)技术详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.类加载器 1>.JVM 类加载机制 如下图所示,JVM类加载机制分为五个部分 ...
- 基础 | batchnorm原理及代码详解
https://blog.csdn.net/qq_25737169/article/details/79048516 https://www.cnblogs.com/bonelee/p/8528722 ...
- Java 反射 设计模式 动态代理机制详解 [ 转载 ]
Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...
- Scala进阶之路-反射(reflect)技术详解
Scala进阶之路-反射(reflect)技术详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Scala中的反射技术和Java反射用法类似,我这里就不一一介绍反射是啥了,如果对 ...
- Oracle中的SQL分页查询原理和方法详解
Oracle中的SQL分页查询原理和方法详解 分析得不错! http://blog.csdn.net/anxpp/article/details/51534006
- Spring学习 6- Spring MVC (Spring MVC原理及配置详解)
百度的面试官问:Web容器,Servlet容器,SpringMVC容器的区别: 我还写了个文章,说明web容器与servlet容器的联系,参考:servlet单实例多线程模式 这个文章有web容器与s ...
- DeepLearning tutorial(3)MLP多层感知机原理简介+代码详解
本文介绍多层感知机算法,特别是详细解读其代码实现,基于python theano,代码来自:Multilayer Perceptron,如果你想详细了解多层感知机算法,可以参考:UFLDL教程,或者参 ...
- Spring之IOC原理及代码详解
一.什么是IOC 引用 Spring 官方原文:This chapter covers the Spring Framework implementation of the Inversion of ...
随机推荐
- python语言的jenkinapi
# coding:utf-8 from jenkinsapi.jenkins import Jenkins # 实例化Jenkins对象,传入地址+账号+密码 j = Jenkins("ht ...
- border使用小技巧
border-style 分类 dashed虚线类型 dotted 点线类型 double 双线类型 双线型量根实线的宽度和中间空白区域的间距有一定规律: 可以利用这个规律画出一些特殊的图案 代码如下 ...
- MVC dropdownlist 下拉框
List<SelectListItem> items = new List<SelectListItem>(); items.Add(new SelectListItem() ...
- 「SHOI2016」黑暗前的幻想乡
题目链接 戳我 \(Describe\) \(n−1\)个公司,每个公司能修一些边,求每条边都让不同的公司来修的生成树的方案数 \(Solution\) 这道题很明显容斥.答案就是:所有都选的生成树个 ...
- jQuery展开收缩2
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...
- 【loj#6503.】「雅礼集训 2018 Day4」Magic(生成函数+容斥)
题面 传送门 题解 复杂度比较迷啊-- 以下以\(n\)表示颜色总数,\(m\)表示总的卡牌数 严格\(k\)对比较难算,我们考虑容斥 首先有\(i\)对就代表整个序列被分成了\(m-i\)块互不相同 ...
- java类型与jdbc类型对应表
java.sql.Types 值 Java 类型 IBM DB2 Oracle Sybase SQL Informix IBM Content Manager BIGINT java.lang ...
- CentOS7-Minimal1708安装设置python3
使用 python -V 命令查看一下是否安装Python然后使用命令 which python 查看一下Python可执行文件的位置可见执行文件在/usr/bin/ 目录下,切换到该目录下执行 ll ...
- 可变参数中size_t遇见的问题
在修改php扩展Trie时,出现了一个小bug PHP_FUNCTION(trie_filter_load) { Trie *trie; char *path; int path_len; if (z ...
- CH6201走廊泼水节
题目链接: CH6201 [简化版题意]给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树.求增加的边的权值总和最小是多少. 输入格式 本题为多组数据~ ...