Golang之路---03 面向对象——反射

反射

反射的存在意义

在开发中,你或许会碰到在有些情况下,你需要获取一个对象的类型,属性及方法,而这个过程其实就是反射。
golang中变量包括(type, value)两部分

  1. 静态类型
    所谓的静态类型(即 static type),就是变量声明的时候的类型。
var age int   // int 是静态类型
var name string  // string 也是静态类型

它是你在编码时,肉眼可见的类型。

  1. 动态类型
    所谓的动态类型(即 concrete type,也叫具体类型)是程序运行时系统才能看见的类型。
var i interface{}
i = 18
i = "Golang"

  第一行:我们在给 i 声明了 interface{} 类型,所以 i 的静态类型就是 interface{}
  第二行:当我们给变量 i 赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。
  第三行:当我们给变量 i 赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。
  从以上,可以知道,不管是 i=18 ,还是 i=“Golang”,都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的动态类型是程序运行时系统才能看见的类型。

  1. 接口组成
      每个接口变量,实际上都是由一 pair 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。
    比如下面这条语句
var age int = 25

我们声明了一个 int 类型变量,变量名叫 age ,其值为 25。

  1. 接口细分
    一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
    根据接口是否包含方法,可以将接口分为 iface 和 eface。
    iface
    第一种:iface,表示带有一组方法的接口。
    比如
type Phone interface {
   call()
}

Golang之路---03 面向对象——反射_第1张图片
  eface
  第二种:eface,表示不带有方法的接口

  比如

var i interface{}

Golang之路---03 面向对象——反射_第2张图片
先来看看 iface,有如下一段代码:

var reader io.Reader

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}

reader = tty

第一行代码:var reader io.Reader ,由于 io.Reader 接口包含 Read 方法,所以 io.Reader 是 iface,此时 reader 对象的静态类型是 io.Reader,暂无动态类型。
Golang之路---03 面向对象——反射_第3张图片
最后一行代码:reader = tty,tty 是一个 *os.File 类型的实例,此时reader 对象的静态类型还是 io.Reader,而动态类型变成了 *os.File。
Golang之路---03 面向对象——反射_第4张图片
再来看看 eface,有如下一段代码:

//不带函数的interface
var empty interface{}

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}

empty = tty

第一行代码:var empty interface{},由于 interface{} 是一个 eface,其只有一个 _type 可以存放变量类型,此时 empty 对象的(静态)类型是 nil。
Golang之路---03 面向对象——反射_第5张图片
最后一行代码:empty = tty,tty 是一个 *os.File 类型的实例,此时 _type 变成了 *os.File。
Golang之路---03 面向对象——反射_第6张图片
由于动态类型的存在,在一个函数中接收的参数的类型有可能无法预先知晓,此时我们就要对参数进行反射,然后根据不同的类型做不同的处理。反射就是用来检测存储在接口变量内部(值value;类型concrete type) pair对的一种机制。

反射中的Type 和 Value

Golang之路---03 面向对象——反射_第7张图片
在真实世界里,type 和 value 是合并在一起组成接口变量(pair对)的。
而在反射的世界里,type 和 data 却是分开的,他们分别由 reflect.Type 和 reflect.Value 来表现。

Golang里有个反射三定律

  1. Reflection goes from interface value to reflection object.

  2. Reflection goes from reflection object to interface value.

  3. To modify a reflection object, the value must be settable.

翻译一下,就是:

反射可以将接口类型变量 转换为“反射类型对象”;

反射可以将 “反射类型对象”转换为 接口类型变量;

如果要修改 “反射类型对象” 其类型必须是 可写的;

第一定律

  为了实现从接口变量到反射对象的转换,需要提到 reflect 包里很重要的两个方法:
  reflect.TypeOf(i) :获得接口值的类型
  reflect.ValueOf(i):获得接口值的值
  这两个方法返回的对象,我们称之为反射对象:Type object 和 Value object。

import (
	"fmt"
	"reflect"
)

func main() {
   var age interface{} = 25
   //%T:输出值的类型  %v: 使用默认格式输出值
   fmt.Printf("原始接口变量的类型为 %T,值为 %v\n",age,age)
   t := reflect.TypeOf(age)
   v := reflect.ValueOf(age)
   //从接口变量到反射对象
   fmt.Printf("从接口变量到反射对象:type对象的类型为 %T\n",t)
   fmt.Printf("从接口变量到反射对象:value对象的类型为 %T\n",v)
}

在这里插入图片描述

第二定律

  完成从反射对象到接口变量的转换
注意:只有 Value 才能逆向转换,而 Type 则不行

func main() {
   var age interface{} = 25
   //%T:输出值的类型  %v: 使用默认格式输出值
   fmt.Printf("原始接口变量的类型为 %T,值为 %v\n",age,age)
   t := reflect.TypeOf(age)
   v := reflect.ValueOf(age)
   //从接口变量到反射对象
   fmt.Printf("从接口变量到反射对象:type对象的类型为 %T\n",t)
   fmt.Printf("从接口变量到反射对象:value对象的类型为 %T\n",v)

   //从反射对象到接口变量
   //通过Interface函数实现
   i := v.Interface()
   fmt.Printf("从接口变量到反射对象:value对象的类型为 %T 值为%v\n",i,i)
}

Golang之路---03 面向对象——反射_第8张图片
最后转换后的对象,静态类型为 interface{} ,如果要转成最初的原始类型,需要再类型断言转换一下

第三定律

  反射世界是真实世界的一个『映射』,是我的一个描述,但这并不严格,因为并不是你在反射世界里所做的事情都会还原到真实世界里。

  第三定律引出了一个 settable (可设置性,或可写性)的概念。

  Golang 语言里的函数都是值传递,只要你传递的不是变量的指针,你在函数内部对变量的修改是不会影响到原始的变量的。

  回到反射上来,当你使用 reflect.Typeof 和 reflect.Valueof 的时候,如果传递的不是接口变量的指针,反射世界里的变量值始终将只是真实世界里的一个拷贝,你对该反射对象进行修改,并不能反映到真实世界里。

因此在反射的规则里还要注意

  1. 不是接收变量指针创建的反射对象,是不具备『可写性』的

  2. 是否具备『可写性』,可使用 CanSet() 来获取得知

  3. 对不具备『可写性』的对象进行修改,是没有意义的,也认为是不合法的,因此会报错。

	var name string = "hello world"
	v := reflect.ValueOf(name)
	fmt.Println("可写性为:",v.CanSet())

在这里插入图片描述
要让反射对象具备可写性,需要注意两点

  1. 创建反射对象时传入变量的指针

  2. 使用 Elem()函数返回指针指向的数据

func main() {
	var name string = "hello world"
	v := reflect.ValueOf(&name)
	fmt.Println("可写性为:",v.CanSet())

	v2 := v.Elem()
    fmt.Println("v2可写性为:",v2.CanSet())
}

在这里插入图片描述

反射中的函数

获取类别:Kind()

  Type 对象 和 Value 对象都可以通过 Kind() 方法返回对应的接口变量的基础类型。

reflect.TypeOf(m).Kind()
reflect.ValueOf(m).Kind()

  在这里,要注意的是,Kind 和 Type 是有区别的,Kind 表示更基础,范围更广的分类。

  有一个例子来表示, iPhone (接口变量)的 Type 是手机,Kind 是电子产品。
   Kind 表示的基本都是 Go 原生的基本类型(共有 25 种的合法类型)

type Kind uint

const (
  Invalid Kind = iota  // 非法类型
  Bool                 // 布尔型
  Int                  // 有符号整型
  Int8                 // 有符号8位整型
  Int16                // 有符号16位整型
  Int32                // 有符号32位整型
  Int64                // 有符号64位整型
  Uint                 // 无符号整型
  Uint8                // 无符号8位整型
  Uint16               // 无符号16位整型
  Uint32               // 无符号32位整型
  Uint64               // 无符号64位整型
  Uintptr              // 指针
  Float32              // 单精度浮点数
  Float64              // 双精度浮点数
  Complex64            // 64位复数类型
  Complex128           // 128位复数类型
  Array                // 数组
  Chan                 // 通道
  Func                 // 函数
  Interface            // 接口
  Map                  // 映射
  Ptr                  // 指针
  Slice                // 切片
  String               // 字符串
  Struct               // 结构体
  UnsafePointer        // 底层指针
)

/* kink函数的用法 */
func main() {
	m := Profile{}
   
	t := reflect.TypeOf(m)
	/* 
	   Type: main.Profile
       Kind  struct */
    fmt.Println("Type:",t)
	//1.传入值
	fmt.Println("Kind ",t.Kind())

	//2.传入指针
	v := reflect.ValueOf(&m)
	/* &m Type : *main.Profile
       &m Kind : ptr
	   m Type : main.Profile
	   m Kind : struct */
	fmt.Println("&m Type :",v.Type())
	fmt.Println("&m Kind :",v.Kind())
	//Elem会返回 Type 对象所表示的指针指向的数据
    fmt.Println("m Type :",v.Elem().Type())
	fmt.Println("m Kind :",v.Elem().Kind())
  
}

进行类型的转换

eg: Int() :转成 int
示例代码如下

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var age int = 25
    v1 := reflect.ValueOf(age)
    fmt.Printf("转换前, type: %T, value: %v \n", v1, v1)
    v2 := v1.Int()
    fmt.Printf("转换后, type: %T, value: %v \n", v2, v2)
}

在这里插入图片描述

对属性的操作

func main() {
   p := Pearson{"xy",21,"male"}

   v:=reflect.ValueOf(p)
   //NumField()
   fmt.Println("字段数:",v.NumField())
   //Field
   fmt.Println("第一个字段:",v.Field(0))
   fmt.Println("第一个字段:",v.Field(1))
   fmt.Println("第一个字段:",v.Field(2))
   
   //通过遍历的方式进行打印
   for i:=0;i<v.NumField();i++{
	fmt.Printf("第 %d个字段:%v\n",i+1,v.Field(i))
   }
}

Golang之路---03 面向对象——反射_第9张图片

对方法的操作

type Pearson struct{
	name string
	age int
	gender string
}

func(p Pearson)SayBye(){
	fmt.Println("Bye")
}

func(p Pearson)SayHello(){
	fmt.Println("Hello")
}

func main() {
   p := &Pearson{"xy",21,"male"}
//注意使用 TypeOf
   v:=reflect.TypeOf(p)
   
   fmt.Println("方法数:",v.NumMethod())
   fmt.Println("第一个方法:",v.Method(0).Name)
   fmt.Println("第一个方法:",v.Method(1).Name)
   
   //通过遍历的方式进行打印
   for i:=0;i<v.NumMethod();i++{
	fmt.Printf("第 %d个方法:%v\n",i+1,v.Method(i).Name)
   }
}

Golang之路---03 面向对象——反射_第10张图片

反射说明

  Golang 的反射很慢,这个和它的 API 设计有关
  不到不得不用的地步,能避免使用反射就不用。

你可能感兴趣的:(Golang,golang,开发语言,后端)