『GoLang』反射
方法和类型的反射
反射是应用程序检查其所拥有的结构,尤其是类型的一种能。每种语言的反射模型都不同,并且有些语言根本不支持反射。Go语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,即可从运行时态的示例对象反求其编码阶段的定义,标准库中reflect包提供了相关的功能。在reflect包中,通过reflect.TypeOf(),reflect.ValueOf()分别从类型、值的角度来描述一个Go对象。
func TypeOf(i interface{}) Type
type Type interface
func ValueOf(i interface{}) Value
type Value struct
在Go语言的实现中,一个interface类型的变量存储了2个信息, 一个<值,类型>对,<value,type> :
(value, type)
value是实际变量值,type是实际变量的类型。两个简单的函数,reflect.TypeOf和reflect.ValueOf,返回被检查对象的类型和值。
例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回 3.4。实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
reflect.Type和reflect.Value都有许多方法用于检查和操作它们。
Type主要有:
Kind()将返回一个常量,表示具体类型的底层类型Elem()方法返回指针、数组、切片、字典、通道的基类型,这个方法要慎用,如果用在其他类型上面会出现panic
Value主要有:
Type()将返回具体类型所对应的reflect.Type(静态类型)Kind()将返回一个常量,表示具体类型的底层类型Elem()返回接口所包含的值,或者,指针指向的值
反射可以在运行时检查类型和变量,例如它的大小、方法和动态的调用这些方法。这对于没有源代码的包尤其有用。
由于反射是一个强大的工具,但反射对性能有一定的影响,除非有必要,否则应当避免使用或小心使用。下面代码针对int、数组以及结构体分别使用反射机制,其中的差异请看注释。
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
}
func main() {
var a int = 50
v := reflect.ValueOf(a) // 返回Value类型对象,值为50
t := reflect.TypeOf(a) // 返回Type类型对象,值为int
fmt.Println(v, t, v.Type(), t.Kind()) // 50 int int int
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(),reflect.TypeOf(b).Elem()) // [5]int array int
var Pupil Student
p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
fmt.Println(p.Type()) // 输出:main.Student
fmt.Println(p.Kind()) // 输出:struct
}
在Go语言中,类型包括 static type 和 concrete type。简单说 static type 是你在编码是看见的类型(如
int、string),concrete type 是实际具体的类型,runtime 系统看见的类型
Type()返回的是静态类型,而 Kind() 返回的是具体类型。上面代码中,在 int,数组以及结构体三种类型情况中,可以看到 kind(),type()返回值的差异。
通过反射可以修改原对象
CanAddr()方法:判断它是否可被取地址CanSet()方法:判断它是否可被取地址并可被修改
是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性
通过一个settable的Value反射对象来访问、修改其对应的变量值:
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
Age int
}
func main() {
var a int = 50
v := reflect.ValueOf(a) // 返回Value类型对象,值为50
t := reflect.TypeOf(a) // 返回Type类型对象,值为int
fmt.Println(v, t, v.Type(), t.Kind(), reflect.ValueOf(&a).Elem()) // 50 int int int 50
seta := reflect.ValueOf(&a).Elem() // 这样才能让seta保存a的值
fmt.Println(seta, seta.CanSet()) // 50 true
seta.SetInt(1000)
fmt.Println(seta) // 1000
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem()) // [5]int array int
var Pupil Student = Student{"joke", 18}
p := reflect.ValueOf(Pupil) // 使用ValueOf()获取到结构体的Value对象
fmt.Println(p.Type()) // 输出:main.Student
fmt.Println(p.Kind()) // 输出:struct
setStudent := reflect.ValueOf(&Pupil).Elem()
//setStudent.Field(0).SetString("Mike") // 未导出字段,不能修改,panic会发生
setStudent.Field(1).SetInt(19)
fmt.Println(setStudent) // {joke 19}
}
虽然反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,但不能修改这些未导出的成员。因为一个结构体中只有被导出的字段才是可修改的(就是对外可见的才可以修改)。
当
v := reflect.ValueOf(x)函数通过传递一个x拷贝创建了v,那么v的改变并不能更改原始的x。要想v的更改能作用到x,那就必须传递x的地址v = reflect.ValueOf(&x)。然后这个是返回的是指针,也是不可修改的,需要再调用Elem()函数,即setx = reflect.ValueOf(&x).Elem(),假如x是int类型的,然后可以setx.SetInt(111)这样修改了
在结构体中有tag标签,通过反射可获取结构体成员变量的tag信息。
package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
Age int `json:"years"`
}
func main() {
var Pupil Student = Student{"joke", 18}
setStudent := reflect.ValueOf(&Pupil).Elem()
sSAge, _ := setStudent.Type().FieldByName("Age")
fmt.Println(sSAge.Tag.Get("json")) // years
}
反射结构体
为了完整说明反射的情况,通过反射一个结构体类型,综合来说明。下面例子较为系统地利用一个结构体,来充分举例说明反射:
package main
import (
"fmt"
"reflect"
)
// 结构体
type ss struct {
int
string
bool
float64
}
func (s ss) Method1(i int) string { return "结构体方法1" }
func (s *ss) Method2(i int) string { return "结构体方法2" }
var (
structValue = ss{ // 结构体
20,
"结构体",
false,
64.0,
}
)
// 复杂类型
var complexTypes = []interface{}{
structValue, &structValue, // 结构体
structValue.Method1, structValue.Method2, // 方法
}
func main() {
// 测试复杂类型
for i := 0; i < len(complexTypes); i++ {
PrintInfo(complexTypes[i])
}
}
func PrintInfo(i interface{}) {
if i == nil {
fmt.Println("--------------------")
fmt.Printf("无效接口值:%v\n", i)
fmt.Println("--------------------")
return
}
v := reflect.ValueOf(i)
PrintValue(v)
}
func PrintValue(v reflect.Value) {
fmt.Println("--------------------")
// ----- 通用方法 -----
fmt.Println("String :", v.String()) // 反射值的字符串形式
fmt.Println("Type :", v.Type()) // 反射值的类型
fmt.Println("Kind :", v.Kind()) // 反射值的类别
fmt.Println("CanAddr :", v.CanAddr()) // 是否可以获取地址
fmt.Println("CanSet :", v.CanSet()) // 是否可以修改
if v.CanAddr() {
fmt.Println("Addr :", v.Addr()) // 获取地址
fmt.Println("UnsafeAddr :", v.UnsafeAddr()) // 获取自由地址
}
// 获取方法数量
fmt.Println("NumMethod :", v.NumMethod())
if v.NumMethod() > 0 {
// 遍历方法
i := 0
for ; i < v.NumMethod()-1; i++ {
fmt.Printf(" ┣ %v\n", v.Method(i).String())
}
fmt.Printf(" ┗ %v\n", v.Method(i).String())
// 通过名称获取方法
fmt.Println("MethodByName :", v.MethodByName("String").String())
}
switch v.Kind() {
// 结构体:
case reflect.Struct:
fmt.Println("=== 结构体 ===")
// 获取字段个数
fmt.Println("NumField :", v.NumField())
if v.NumField() > 0 {
var i int
// 遍历结构体字段
for i = 0; i < v.NumField()-1; i++ {
field := v.Field(i) // 获取结构体字段
fmt.Printf(" ├ %-8v %v\n", field.Type(), field.String())
}
field := v.Field(i) // 获取结构体字段
fmt.Printf(" └ %-8v %v\n", field.Type(), field.String())
// 通过名称查找字段
if v := v.FieldByName("ptr"); v.IsValid() {
fmt.Println("FieldByName(ptr) :", v.Type().Name())
}
// 通过函数查找字段
v := v.FieldByNameFunc(func(s string) bool { return len(s) > 3 })
if v.IsValid() {
fmt.Println("FieldByNameFunc :", v.Type().Name())
}
}
}
}
输出结果:
--------------------
String : <main.ss Value>
Type : main.ss
Kind : struct
CanAddr : false
CanSet : false
NumMethod : 1
┗ <func(int) string Value>
MethodByName : <invalid Value>
=== 结构体 ===
NumField : 4
├ int <int Value>
├ string 结构体
├ bool <bool Value>
└ float64 <float64 Value>
--------------------
String : <*main.ss Value>
Type : *main.ss
Kind : ptr
CanAddr : false
CanSet : false
NumMethod : 2
┣ <func(int) string Value>
┗ <func(int) string Value>
MethodByName : <invalid Value>
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
--------------------
String : <func(int) string Value>
Type : func(int) string
Kind : func
CanAddr : false
CanSet : false
NumMethod : 0
structValue,&structValue的反射结果是不一样的,指针对象在这里有两个方法,而值对象只有一个方法,这是因为Method2()方法是指针方法,在值对象中是不能被反射到的。
我们同样能够调用签名在结构上的方法,例如,使用索引
n来调用:Method(n).Call(XXXX),具体传参怎么传再说。
『GoLang』反射的更多相关文章
- 『Golang』Martini框架入门
本文介绍golang中的优秀web开发框架martini! 序 Martini框架是使用Go语言作为开发语言的一个强力的快速构建模块化web应用与服务的开发框架.Martini是一个专门用来处理Web ...
- 『Golang』MongoDB在Golang中的使用(mgo包)
有关在Golang中使用mho进行MongoDB操作的最简单的例子.
- 『Golang』在Golang中使用json
由于要开发一个小型的web应用,而web应用大部分都会使用json作为数据传输的格式,所以有了这篇文章. 包引用 import ( "encoding/json" "gi ...
- 『Golang』—— 标准库之 os
Golang 的 os 库基本承袭 Unix 下 C 语言的用法 path 库: func Base(path string) string //取文件名,不含目录部分 func Dir(path s ...
- 『GoLang』fmt包的使用
目录 1. fmt 包初识 2. 格式化 verb 应用 2.1 通用 2.2 布尔值 2.3 整数 2.4 浮点数与复数 2.5 字符串和 []byte 2.6 指针 2.7 其他 flag 2.8 ...
- 『GoLang』string及其相关操作
目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...
- 『GoLang』结构体与方法
结构体 结构体类型 Go 通过结构体的形式支持用户自定义类型,或者叫定制类型. Go 语言结构体是实现自定义类型的一种重要数据类型. 结构体是复合类型(composite types),它由一系列属性 ...
- 『Golang』跨平台TUI(基于文字的用户界面)库Terbox-Go文档翻译
原文 package termbox import "github.com/nsf/termbox-go" termbox-go 是一个用于创建跨平台TUI(基于文本的用户界面)的 ...
- 『Golang』Go简介以及环境搭建
简介 go语言是由Google进行维护的一个编程语言,发布自2009年.其以良好的编程风格.优秀的并发机制被广大的技术人员所接受. 使用go语言开发的优秀的产品: Docker gocode lime ...
随机推荐
- 3 分钟了解 JSON Schema
大家好,我不是鱼皮. 幸运又不幸,我是一名程序员,他也是一名程序员. 周末,我在开发网站,他在开发游戏,两个人一起写代码,一起写 Bug 头秃,竟也有了一丝别样的浪漫,好不自在! 今天,他遇到了一个后 ...
- NOIP 模拟 9 斐波那契
题解 这是一道推规律的题. 首先,这道题送分不少,先考虑 \(70pts\),直接暴力 \(\mathcal O(n)\) 建边,\(\mathcal O(logn)\) 求 \(lca\) 其次对于 ...
- 伪静态是什么?伪静态与普通html静态网页区别?
什么是伪静态,伪静态作用伪静态即是网站本身是动态网页如.php..asp..aspx等格式动态网页有时这类动态网页还跟"?"加参数来读取数据库内不同资料.很典型的案例即是discu ...
- 本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用。
本文详细阐述如何用C#创建COM组件,并能用VC6.0等调用. 附:本文适用任何VS系列工具. 在用C#创建COM组件时,一定要记住以下几点: 1.所要导出的类必须为公有: 2.所有属性.方法也必须为 ...
- C++11 shared_ptr智能指针(超级详细)
在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃.程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的.比如: 有些内存资源已经被释放,但指向它的指针并没 ...
- 一、web请求
BS架构(Browser/Server) 客户端使用统一的浏览器(Browser) 服务端(Server)基于统一的HTTP协议 流程:用户浏览器输入URL地址–>DNS域名解析出IP地址–&g ...
- CAS 的ABA 问题
CAS CAS:Compare and Swap, 翻译成比较并交换. java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁. 其原理是CAS有 ...
- Learning ROS: rqt_console和rqt_logger_level使用
rqt_console:操作.查看log信息 rqt_logger_level:设置log等级 打开node: rosrun rqt_console rqt_console rosrun rqt_lo ...
- 使用Keepalived实现Nginx的双机主备高可用
1.概述 前面我们聊过使用 Nginx 为 后端Tomcat 做负载均衡.高可用,但是这时Nginx又成了单点,如果Nginx不幸挂掉,整个网站便无法访问. 此时我们就会用到另一个软件 -- Keep ...
- (八)羽夏看C语言——C番外篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章 ...