方法和类型的反射

反射是应用程序检查其所拥有的结构,尤其是类型的一种能。每种语言的反射模型都不同,并且有些语言根本不支持反射。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.TypeOfreflect.ValueOf,返回被检查对象的类型和值。

例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64reflect.ValueOf(x) 返回 3.4。实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

reflect.Typereflect.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 typeconcrete type。简单说 static type 是你在编码是看见的类型(如intstring),concrete type 是实际具体的类型,runtime 系统看见的类型

Type()返回的是静态类型,而 Kind() 返回的是具体类型。上面代码中,在 int,数组以及结构体三种类型情况中,可以看到 kind()type()返回值的差异。

通过反射可以修改原对象

  • CanAddr()方法:判断它是否可被取地址

  • CanSet()方法:判断它是否可被取地址并可被修改

    是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性

通过一个settableValue反射对象来访问、修改其对应的变量值:

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(),假如 xint 类型的,然后可以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』反射的更多相关文章

  1. 『Golang』Martini框架入门

    本文介绍golang中的优秀web开发框架martini! 序 Martini框架是使用Go语言作为开发语言的一个强力的快速构建模块化web应用与服务的开发框架.Martini是一个专门用来处理Web ...

  2. 『Golang』MongoDB在Golang中的使用(mgo包)

    有关在Golang中使用mho进行MongoDB操作的最简单的例子.

  3. 『Golang』在Golang中使用json

    由于要开发一个小型的web应用,而web应用大部分都会使用json作为数据传输的格式,所以有了这篇文章. 包引用 import ( "encoding/json" "gi ...

  4. 『Golang』—— 标准库之 os

    Golang 的 os 库基本承袭 Unix 下 C 语言的用法 path 库: func Base(path string) string //取文件名,不含目录部分 func Dir(path s ...

  5. 『GoLang』fmt包的使用

    目录 1. fmt 包初识 2. 格式化 verb 应用 2.1 通用 2.2 布尔值 2.3 整数 2.4 浮点数与复数 2.5 字符串和 []byte 2.6 指针 2.7 其他 flag 2.8 ...

  6. 『GoLang』string及其相关操作

    目录 1. 字符串简介 2. 字符串的拼接 3. 有关 string 的常用处理 3.1 strings 包 3.1.1 判断两个 utf-8 编码字符串是否相同 3.1.2 判断字符串 str 是否 ...

  7. 『GoLang』结构体与方法

    结构体 结构体类型 Go 通过结构体的形式支持用户自定义类型,或者叫定制类型. Go 语言结构体是实现自定义类型的一种重要数据类型. 结构体是复合类型(composite types),它由一系列属性 ...

  8. 『Golang』跨平台TUI(基于文字的用户界面)库Terbox-Go文档翻译

    原文 package termbox import "github.com/nsf/termbox-go" termbox-go 是一个用于创建跨平台TUI(基于文本的用户界面)的 ...

  9. 『Golang』Go简介以及环境搭建

    简介 go语言是由Google进行维护的一个编程语言,发布自2009年.其以良好的编程风格.优秀的并发机制被广大的技术人员所接受. 使用go语言开发的优秀的产品: Docker gocode lime ...

随机推荐

  1. PostgreSQL执行计划的解析

    一个顺序磁盘页面操作的cost值由系统参数seq_page_cost (floating point)参数指定的,由于这个参数默认为1.0,所以我们可以认为一次顺序磁盘页面操作的cost值为1.下面o ...

  2. Fancytree实例

    一.实例 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> ...

  3. Redis缓存雪崩、缓存穿透、热点key

    转载自  https://blog.csdn.net/wang0112233/article/details/79558612 https://www.sohu.com/a/230787856_231 ...

  4. 漫画CAS 的ABA 问题

    链接:https://blog.csdn.net/bjweimengshu/article/details/79000506

  5. freeswitch新增模块API

    概述 上一章我们讲解了freeswitch的源码基本结构,以及如何新增一个插件式模块. freeswitch的架构非常适合这种业务开发模式,即以freeswitch的基本功能为开发平台,新增插件式模块 ...

  6. Java如何调用C语言程序,JNI技术

    Java为什么要调用C语言编写的程序因为涉及操作系统底层的事件,Java是处理不了的,例如用户上传一个视频文件,需要后台给视频加上水印,或者后台分离视频流和音频流,这个事Java就做不了,只能交给C语 ...

  7. 两种github action 打包.Net Core 项目docker镜像推送到阿里云镜像仓库

    两种github action 打包.Net Core 项目docker镜像推送到阿里云镜像仓库 1.GitHub Actions 是什么? 大家知道,持续集成由很多操作组成,比如抓取代码.运行测试. ...

  8. tomcat漏洞总结

    描述 Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache.Sun 和其他一些公司及个人共同开发而成.由 ...

  9. hibernate01

    什么是hibernate ORM框架/持久层框架 jdbc的一个框架 object reference mapping 通过管理对象来改变数据库中的数据 通过管理对象来操作数据库 hibernate的 ...

  10. python模块--calendar

    方法 返回值类型 说明 .calendar(theyear, w=2, l=1, c=6, m=3) str 返回指定年份的年历, w: 每个日期的宽度, l: 每一行的纵向宽度, c: 月与月之间的 ...