关于golang:Go系列之-反射

9次阅读

共计 3725 个字符,预计需要花费 10 分钟才能阅读完成。

反射加强了语言的动静形容能力,你要问我什么是动静,我只能说,所有可能产生意料之外情理之中的变动,都是动静。

概述

反射这个词并不是特定语言持有的,相同很多语言领有着本人的反射模型。诚实说,我并不喜爱去用业余的术语去解释一些概念性的货色,这样往往观看的人也云里雾里,这些概念性的货色,每个人脑海中都有本人的“解释语言“,随他去吧。

我次要想谈谈为什么须要反射,利用场景是什么?其实在我看来,这两个问题严格意义上是等价的,即 为什么 = 利用场景,利用场景 = 为什么。

go 作为动态类型语言,如果没有反射,很多可能作用于 “ 任意类型 ” 的函数,实现起来会很麻烦。我举一个最简略的场景:


package main

// 会员
type member struct {
  Name  string
  Level int
}

// 游客
type visitor struct {NickName string}

func main() {
  m := member{
    Name:  "wqq",
    Level: 520,
  }
  v := visitor{NickName: "curry",}
  checkSomeTing(m)
  checkSomeTing(v)
}

func checkSomeTing(v interface{}) {
  if "如果是会员的话" {// ...} else {// ...}
}

 

下面 member 和 visitor 两种构造体示意两种身份,他们都须要通过公共的 checkSomeTing 操作,咱们心愿在这个函数中能依据不同的构造体,操作不同的逻辑。如果没有反射,如何晓得传进来具体是什么类型的值。(我只是单纯的指出没有反射状况下的问题,而不是吐槽下面这个设计)。

因而,咱们须要能有一种办法,它能够在程序运行时获取传入参数真正的类型,如果是 struct 那么这个 struct 有哪些属性,哪些办法 ……。

Interface

说起 go 反射,就必然绕不开 interface 类型。在 go 中 interface 是一种非凡的类型,能够寄存任何实现了其办法的值。如果是一个空 interface,意味着能够传递任意类型值。interface 类型有(value,type)  对,而反射就是查看 interface 的 (value,type) 对,所有的反射包中的办法,都是围绕 reflect.Type 和 reflect.Value 进行的。reflect.Type 是一个接口类型,提供了一系列获取 interface 信息的接口。源码地位在 src/reflect/type.go。


type Type interface {
  // Methods applicable to all types.
  Align() int
  // FieldAlign returns the alignment in bytes of a value of
  // this type when used as a field in a struct.
  FieldAlign() int
  Method(int) Method
  String() string
  ........
}

而 reflect.Value 的类型被申明成构造体。源码地位 src/reflect/value.go

type Value struct {
  // typ holds the type of the value represented by a Value.
  typ *rtype
  // Pointer-valued data or, if flagIndir is set, pointer to data.
  // Valid when either flagIndir is set or typ.pointers() is true.
  ptr unsafe.Pointer

  flag
}
 

能够看到,这个构造体的三个字段都是公有的,没有对外裸露任何字段,然而它提供了一系列获取数据和写入等操作的办法。

反射三大定律

go 官网提供了三大定律来阐明反射,咱们也从这三个定律中学习如何 应用反射。

  • 定律一: 反射能够将 interface 变量转换为反射对象。

package main

import (
  "fmt"
  "reflect"
)

func main() {
  var a float64 = 32
  var b int64 = 32
  doSomeTing(a)
  doSomeTing(b)
}

func doSomeTing(res interface{}) {t := reflect.TypeOf(res)
  v := reflect.ValueOf(res)
  fmt.Println("类型是:", t)
  fmt.Println("值是:", v)
}

程序打印后果:

咱们定义了两个变量,他们的类型别离是 float64 和 int64,传入 doSomeTing 函数,此函数参数类型为空的 interface,因而能够接管任意类型参数,最终咱们通过 reflect.TypeOf 获取了变量的实在类型,通过 reflect.ValueOf 获取变量实在的值。

咱们能够再试试通过构造体应用其余操作方法。

package main

import (
  "fmt"
  "reflect"
)

type user struct {
  Name string
  Age  int
}

func main() {
  var u = user{
    Age:  18,
    Name: "wuqq",
  }
  v := reflect.ValueOf(u)
  t := reflect.TypeOf(u)
  
  for i := 0; i < t.NumField(); i++ {filed := t.Field(i)
    value := v.Field(i).Interface()
    fmt.Printf("field:%v type:%v value:%v\n", filed.Name, filed.Type, value)
  }
}

 

运行后果:

下面就不解释了,次要解释循环外面的,咱们通过 reflect.type 的 NumField 获取构造体中个数,通过 reflect.type  的 Field 办法下标获取属性名,通过 interface() 获取对应属性值。

  • 定律二: 反射能够将反射对象还原成 interface 对象。
package main

import (
  "fmt"
  "reflect"
)

type user struct {
  Name string
  Age  int
}

func main() {
  var u = user{
    Age:  18,
    Name: "wuqq",
  }
  v := reflect.ValueOf(u)

  var user2 user = v.Interface().(user)
  fmt.Printf("用户:%+v\n",user2)
}
 

u 变量转换成反射对象 v,v 又通过 interface() 接口转换成 interface 对象,再通过显性类型转换成 user 构造体对象,赋值给类型为 user 的变量 user2。

  • 定律三: 反射对象是否可批改,取决于 value 值是否可设置

咱们在通过反射将任意类型的变量 (不论什么类型最终传递到 reflect.TypeOf 或者 reflect.ValueOf 都会隐式转换成 interface) 转换成反射对象,那么实践上咱们就能够基于反射对象设置其所持有的值。


type user struct {
  Name string
  Age  int
}

func main() {
  var u = user{
    Age:  18,
    Name: "wuqq",
  }
  v := reflect.ValueOf(u)
  v.FieldByName("Name").SetString("curry")
    fmt.Printf("v 的值:%+v",v)
}

下面代码咱们想的是通过反射对象批改构造体属性 Name 值为 curry。当运行这段代码时,会报运行恐慌(panic)。

谬误的起因正是值是不可批改的。

反射对象是否可批改取决于其所存储的值,下面 reflect.ValueOf 传入的其实是 u 的值,而非它的自身。(想想函数传值的时候是传值还是传址),既然是传值,那么通过批改 v 的值当然不可能批改到 u 的值,咱们要设置的应该是指针所指向的内容,即 *u。


type user struct {
  Name string
  Age  int
}

func main() {
  var u = user{
    Age:  18,
    Name: "wuqq",
  }
  v := reflect.ValueOf(&u)
  v.Elem().FieldByName("Name").SetString("curry")
  fmt.Printf("v 的值:%+v",v)
}

首先咱们通过 &u 传入 u 变量理论存储的地址。而后通过反射对象中的 Elem() 取得指针所指向的 value。

运行后果值未然被批改。

结尾

对于反射,我还想说说它不好的中央:

  • 作为动态语言,编码过程中,编译器能够提前发现一些类型谬误,然而反射代码是不行的(如果能够请告知)。可能会因为 bug 导致运行恐慌。
  • 反射对性能影响比拟大,对于一些重视运行效率的关键点,尽量避免应用反射。

还有其余乏味的操作,举荐先多看几遍官网的一篇博客:

https://blog.golang.org/laws-…

参考资料:

https://draveness.me/golang/d…

https://www.bookstack.cn/read…

正文完
 0