反射(reflect)机制

滞波柯里化
• 阅读 1366

什么是反射

官方对此有个非常简明的介绍,两句话耐人寻味:

  1. 反射提供一种让程序检查自身结构的能力
  2. 反射是困惑的源泉

要深刻理解反射,个人感觉需要花时间在官方博客上再加以练习,循序渐进,慢慢体会。

反射的三个定律

反射就是检查interface的(value, type)对的,因为任何类型的变量或方法都是实现了空接口。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type.

官方提供了三条定律来说明反射,比较清晰,下面也按照这三定律来总结。

反射包里有两个接口类型要先了解一下.

  • reflect.Type 提供一组接口处理interface的类型,即(value, type)中的type
  • reflect.Value提供一组接口处理interface的值,即(value, type)中的value

下面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。

  • reflect.Type 类型对象
  • reflect.Value类型对象

1. 反射的第一定律:反射可以将interface类型变量转换成为reflect类型变量

这里的reflect类型指的是reflect.Type和reflect.Value类型的变量

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 8.5
    t := reflect.TypeOf(x)  // 这里的t类型为:reflect.Type
    fmt.Println("type:", t)
    v := reflect.ValueOf(x) // 这里的v类型为:reflect.Value
    fmt.Println("value:", v)
}

2. 反射第二定律:反射可以将reflect类型对象还原成interface类型对象

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 8.5
    v := reflect.ValueOf(x) //这里v的类型为:reflect.Value
    var y float64 = v.Interface().(float64) //v通过Interface()函数将反射类型转换为interface类型变量,再通过断言为float64获取值
    fmt.Println("value:", y)
}

3. 反射第三定律:如果要修改reflect类型对象,则value必须是可设置的

package main

import (
    "reflect"
    "fmt"
)

func main() {
    var x float64 = 8.5
    fmt.Println("x :", x)
    v := reflect.ValueOf(&x)
    fmt.Println("settability of v:", v.CanSet())
    //v.SetFloat(5.8) // 这里会报panic的错误,因为这时候v是不可设置的
    fmt.Println("settability of v:", v.Elem().CanSet())
    v.Elem().SetFloat(5.8)
    fmt.Println("x :", v.Elem().Interface().(float64))
}

上面11行v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是*v。 那怎么通过v修改x的值呢?

reflect.Value提供了Elem()方法,可以获得指针向指向的value,也就是第14行和第15行的操作。

反射的使用

1. 查看结构体类型、字段、方法、匿名字段、字段tag

package main

import (
    "fmt"
    "reflect"
)

//定义结构体
type User struct {
    Id int
    Name string 
    Age int
}

type Boy struct {
    User
    Addr string `db:"addr"`
}

//绑定方法
func (u User) Say() {
    fmt.Println("Hello")
}

func (u User) Eat() {
    fmt.Println("Eat price")
}

func PrintInfo(i interface{}){
    t := reflect.TypeOf(i)
    fmt.Println("结构体类型:", t)
    v := reflect.ValueOf(i)
    fmt.Println("结构体的值:", v)

    fmt.Println("\n结构体字段名称   :字段类型 \t : 值 \t\t : 是否为匿名字段: 字段tag为db的值")
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i)
        fmt.Printf("%s\t\t : %v\t : %v\t : %v\t : %v\n", f.Name, f.Type, val.Interface(), f.Anonymous, f.Tag.Get("db"))
        //fieldByName, _ := t.FieldByName(f.Name)
        //fmt.Println(fieldByName)
    }
    fmt.Println("\n结构体绑定的方法:方法类型\t:方法地址 \t\t 方法索引 ")
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Printf("%s\t\t %v\t %v\t %v\n", m.Name, m.Type, &m.Func, m.Index)
        //Func, _ := t.MethodByName(m.Name)
        //fmt.Println(Func)
    }
}

func main() {

    b := Boy{
        User: User{
            Id:   1,
            Name: "Yuan",
            Age:  26,
        },
        Addr: "chengdu",
    }
    PrintInfo(b)
}

2. 修改结构体字段的值

package main

import (
    "fmt"
    "reflect"
)

//定义结构体
type User struct {
    Id int
    Name string
    Age int
}

func SetValue(i interface{}){
    v := reflect.ValueOf(i)
    //获取指针指向的元素
    elem:= v.Elem()
    //获取要修改的字段
    name := elem.FieldByName("Name")
    if name.Kind() == reflect.String && name.CanSet(){
        name.SetString("老大")
    }
}

func main() {

    u := User{
        Id:   1,
        Name: "Yuan",
        Age:  26,
    }
    fmt.Println("修改前:", u)
    SetValue(&u)
    fmt.Println("修改后:", u)
}

3. 调用结构体绑定的方法

package main

import (
    "fmt"
    "reflect"
)

//定义结构体
type User struct {
    Id int
    Name string
    Age int
}

//绑定方法
func (u User) Say(s string) {
    fmt.Println("Hello", s)
}

func (u User) Eat() {
    fmt.Println("Eat price.")
}

func CallFunc(i interface{}){
    v := reflect.ValueOf(i)
    //获取结构体绑定的方法
    sayFunc := v.MethodByName("Say")
    //构建调用参数
    args := []reflect.Value{reflect.ValueOf("world!")}
    //调用方法
    sayFunc.Call(args)

    //调用无参方法
    eatFunc := v.MethodByName("Eat")
    args = []reflect.Value{}
    eatFunc.Call(args) //这里必须传值,不能为空
}

func main() {

    u := User{
        Id:   1,
        Name: "Yuan",
        Age:  26,
    }
    CallFunc(&u)
}

反射的牛刀小试

在Kubernetes中的资源配置文件的主要形式是yaml格式的,同时Kubernetes还支持json格式和proto格式的配置文件,下面我们自己可以实现一个简单的ini格式配置文件的解析,使用案例如下:

package main

func main() {

    // 制作测试数据
    var conf Config
    conf.ServerConf.IP = "192.168.0.1"
    conf.ServerConf.Port = 8080
    conf.ClientConf.Username = "Yuan"
    conf.ClientConf.Password = "Abcd123456"

    //将结构体信息写入配置文件
    StructToFile("./config.ini", conf)

    var config Config
    // 从配置文件解析到结构体中
    FileToStruct("./config.ini", &config)
    logger.Printf("%#v", config)
}

执行上面的代码会生成config.ini配置文件并输出从配置文件解析的结构体信息到控制台

其中生成的config.in文件内容如下:

[SERVER]
ip = 192.168.0.1
port = 8080

[CLIENT]
username = Yuan
password = Abcd123456

控制台输出为:

main.go:18: main.Config{ServerConf:main.ServerConfig{IP:"192.168.0.1", Port:8080}, ClientConf:main.ClientConfig{Username:"Yuan", Password:"Abcd123456"}}

项目源码地址

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
java反射大全
作者对反射的理解:方法的调用(正常的调用:对象.方法()。反射调用方法:方法.对象())静态属性的调用(正常的调用:类.属性。反射调用:属性.类)常见反射的用法:        1.通过反射获取类Class<?demo1Class
Wesley13 Wesley13
3年前
java反射
packagecom.reflect;importjava.lang.reflect.InvocationTargetException;/\\\反射学习\反射的功能就是类、对象,可以通过反射获取里面的方法、属性的功能\@authorAdministrator\\/public
Wesley13 Wesley13
3年前
java的反射机制
java中的反射可以将代码结构更加灵活,通过反射机制可以访问属性、方法和构造方法sun公司为我们提供的4大类反射:java.lang.reflect.methodjava.lang.Classjava.lang.reflect.modifierjava.lang.reflect.Constructor有以下几种方式:比如是Employ
Wesley13 Wesley13
3年前
Unity Reflection Probe使用入门
贴官方API的说法:反射探头:一个反射探头很像一个相机,捕获了周围所有方向的球形视图。然后将捕获的图像存储为Cubemap,可由具有反射材料的对象使用。在给定场景中可以使用多个反射探测器___,可以将对象设置为使用最近的探针生成的立方体贴图。结果是,物体上的反射会根据其环境令人信服地发生变化。______!(https://img
Wesley13 Wesley13
3年前
C#进阶之路(七)反射的应用
反射在C中的应用还是很多的,但它对代码的性能有一定影响。反射的性能:  使用反射来调用类型或者触发方法,或者访问一个字段或者属性时clr需要做更多的工作:校验参数,检查权限等等,所以速度是非常慢的。所以尽量不要使用反射进行编程,对于打算编写一个动态构造类型(晚绑定)的应用程序,可以采取以下的几种方式进行代替:1、通过类的继承关系。让该
Wesley13 Wesley13
3年前
Java反射技术概述
1.什么是Java反射?  就是正在运行,动态获取这个类的所有信息2.反射机制的作用  a.反编译:.class.java  b.通过反射机制,访问Java对象的属性,方法,构造方法等3.反射机制的应用场景  Jdbc加载驱动  SpringIOC实现  Java框架4.创建对象的两种方式  a.直
Wesley13 Wesley13
3年前
Java提高班(六)反射和动态代理(JDK Proxy和Cglib)
反射和动态代理放有一定的相关性,但单纯的说动态代理是由反射机制实现的,其实是不够全面不准确的,动态代理是一种功能行为,而它的实现方法有很多。要怎么理解以上这句话,请看下文。一、反射反射机制是Java语言提供的一种基础功能,赋予程序在运行时<strong自省</strong(introspect,官方用语)的能力。通过反射我们可以直接
Wesley13 Wesley13
3年前
Java反射的使用姿势一览
反射的学习使用日常的学习工作中,可能用到反射的地方不太多,但看看一些优秀框架的源码,会发现基本上都离不开反射的使用;因此本篇博文将专注下如何使用本片博文布局如下:1.反射是什么,有什么用,可以做什么2.如何使用反射3.实例:利用反射方式,获取一个类的所有成员变量的
Wesley13 Wesley13
3年前
Java重点基础:反射机制
一、什么是反射?Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。二、反射的三种方式
深入理解java反射机制及应用 | 京东物流技术团队
因为最近项目中经常有java反射的使用,而其中的IOC、动态代理用到了反射,因此趁这个机会来总结一下关于Java反射的一些知识,复习一下。本篇基于JDK1.8。java反射机制是什么反射原理Java反射机制(JavaReflection)是Java的特征之