zoukankan      html  css  js  c++  java
  • Go语言备忘录(2):反射的原理与使用详解

    本文内容是本人对Go语言的反射原理与使用的备忘录,记录了关键的相关知识点,以供翻查。 文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录(2):反射的原理与使用详解,多谢! 参考书籍《The Go Programming Language》、《Go In Action》、《Go语言学习笔记》等

    目录:

    1. 预备知识
    2. reflect.Typeof、reflect.ValueOf
    3. Value、Type
    4. 动态调用
    5. 通过反射可以修改原对象
    6. 实现类似“泛型”的功能
     
    1.预备知识:
    • 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并返回;
    3.reflect包的两个主要类型Value、Type:这两种类型都提供了大量的方法让我们可以检查和操作这两种类型
    • 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;
    4.通过反射可以动态调用原对象的导出方法:
     
    v := reflect.ValueOf(&x)
    m := v.MethodByName("Show")
    in := []reflect.Value{
        reflect.ValueOf(23),
        reflect.ValueOf(323),
    }
    out := m.Call(in)  //对于变参可用CallSlice方法
     
    5.通过反射可以修改原对象:
    • 原理:
      • 因为给Go的函数、方法传递的都是形参的副本,同样的,反射一个对象时,形参被保存为一个接口对象并作为参数传递(复制),该接口变量是non-settable的,返回的Value也是non-settable的,对它调用Set方法会出现错误;
      • Value的CanSet方法用于测试一个Value的Settablity性质,它有点像unaddressability,但是更加严格,描述的是一个反射对象能够修改创造它的那个实际存储的值的能力。settability由反射对象是否保存原始项而决定。
      • 所以,如果想通过反射来修改对象,必须先把该对象的指针传给reflect.ValueOf(&x),这样得到的Value对象内部就保存了原对象指针的副本,只有找到该指针指向的值才能修改原始对象,通过Elem()方法就可以获得一个保存了原对象的Value对象,此时的Value对象就是settable的;
    对于一个settable的Value反射对象,如 d := reflect.ValueOf(&x).Elem():
    • d.CanAddr()方法:判断它是否可被取地址
    • d.CanSet()方法:判断它是否可被取地址并可被修改
    通过一个settable的Value反射对象来访问、修改其对应的变量的方式:
    • 方式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
    ", 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}
     
    6.反射库提供了内置函数make和new的对应操作,如reflect.MakeFunc()方法,通过它可以实现类似“泛型”的功能:
    • 定义一个可适应不同数据类型的通用模板算法函数,然后用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!
    }
     
     最后,反射对性能有一定的影响,如对性能要求较高,须谨慎使用反射!
     
  • 相关阅读:
    Elasticsearch Query DSL 整理总结(三)—— Match Phrase Query 和 Match Phrase Prefix Query
    Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了
    Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述
    Elasticsearch Java Rest Client API 整理总结 (三)——Building Queries
    Elasticsearch date 类型详解
    python 历险记(五)— python 中的模块
    python 历险记(四)— python 中常用的 json 操作
    python 历险记(三)— python 的常用文件操作
    Elasticsearch Java Rest Client API 整理总结 (二) —— SearchAPI
    Elasticsearch Java Rest Client API 整理总结 (一)——Document API
  • 原文地址:https://www.cnblogs.com/susufufu/p/7653579.html
Copyright © 2011-2022 走看看