# Golang 反射

# 一、Golang 反射简介

# 1. 反射定义

官网解释:

Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.

在计算机领域,反射是一种让程序(主要是通过类型)理解其自身结构的一种能力。它是元编程的组成之一,同时它也是一大引人困惑的难题。

维基百科:

在计算机科学中,反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

不同语言的反射模型不尽相同,有些语言还不支持反射。

《Go 语言圣经》中是这样定义反射的:

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

# 2. 反射场景

  1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

但是对于反射,还是有几点不太建议使用反射的理由:

  1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

# 3. 反射基础

反射是如何实现的?我们以前学习过 interface,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。在进行更加详细的了解之前,我们需要重新温习一下Go语言相关的一些特性,所谓温故知新,从这些特性中了解其反射机制是如何使用的。

特点 说明
Go语言是静态类型语言 编译时类型已经确定,比如对已基本数据类型的再定义后的类型,反射时候需要确认返回的是何种类型。
空接口 interface{} Go 的反射机制是要通过接口来进行的,而类似于 Java 的 Object 的空接口可以和任何类型进行交互,因此对基本数据类型等的反射也直接利用了这一特点。

Go 语言变量包含了两个部分:

  • type
    • type 包括 static typeconcrete type。简单来说 static type 是你在编码是看见的类型(如 int、string),concrete type 是 runtime 系统看见的类型。
    • 类型断言能否成功,取决于变量的 concrete type,而不是 static type。因此,一个 reader 变量如果它的 concrete type 也实现了 write 方法的话,它也可以被类型断言为 writer。
    • Go 是静态类型语言。每个变量都拥有一个静态类型,这意味着每个变量的类型在编译时都是确定的:int,float32, *AutoType, []byte, chan []int 诸如此类。
  • value
    • 变量的值。

在反射的概念中, 编译时就知道变量类型的是静态类型,运行时才知道一个变量类型的叫做动态类型。

  • 静态类型

    type MyInt int // int 就是静态类型
    
  • 动态类型

    运行时给这个变量赋值时,这个值的类型(nil 的时候没有动态类型)。

    一个变量的动态类型在运行时可能改变,这主要依赖于它的赋值(前提是这个变量是接口类型)。

    var A interface{} // 静态类型 interface{}
    A = 10            // 静态类型为 interface{}  动态为 int
    A = "String"      // 静态类型为 interface{}  动态为 string
    var M *int
    A = M             // A 的值可以改变
    

Go 语言的反射就是建立在类型之上的,Go 的指定类型的变量的类型是静态的(也就是指定 int、string 这些的变量,它的 type 是 static type),在创建变量的时候就已经确定,反射主要与 Go 的 interface 类型相关(它的 type 是 concrete type), 只有 interface 类型才有反射一说。

# 4. Type & Value

在 Go 的实现中,每个 interface 变量都有一个对应 pairpair 中记录了实际变量的值和类型:

(value, type)
  • value

    实际变量值。

  • type

    是实际变量的类型。一个 interface{} 类型的变量包含了 2 个指针,一个指针指向值的类型(concrete type),另外一个指针指向实际的值(value)。

跟反射相关的包叫 reflect,它提供以下 2 个函数:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
// ValueOf 用来获取输入参数接口中的数据的值,如果接口为空则返回 0
func ValueOf(i interface{}) Value {...}

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
// TypeOf 用来动态获取输入参数接口中的值的类型,如果接口为空则返回 nil
func TypeOf(i interface{}) Type {...}

示例代码:

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// 反射操作:通过反射,可以获取一个接口类型变量的 type 和 value
	var x float64 = 3.4

	fmt.Println("type:", reflect.TypeOf(x))
	fmt.Println("value:", reflect.ValueOf(x))

	fmt.Println("----------------------------------")

	// 根据反射的值,来获取对应的类型和数值
	v := reflect.ValueOf(x)
	fmt.Println("kind is:", v.Kind())
	fmt.Println("type:", v.Type())
	fmt.Println("value:", v.Float())
}

输出:

type: float64
value: 3.4
----------------------------------
kind is: float64
type: float64
value: 3.4

说明:

  1. reflect.TypeOf:直接给到了我们想要的 type 类型,如 float64、int、各种pointer、struct 等等真实的类型。
  2. reflect.ValueOf:直接给到了我们想要的具体的值,如 1.2345 这个具体数值,或者类似 &{1 "Allen.Wu" 25} 这样的结构体 struct 的值。
  3. 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是 reflect.Type 和 reflect.Value 这两种。

Type 和 Value 都包含了大量的方法,其中第一个有用的方法应该是 Kind,这个方法返回该类型的具体信息:Uint、Float64 等。Value 类型还包含了一系列类型方法,比如 Int(),用于返回对应的值。以下是Kind的种类:


// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

# 二、Golang 反射 reflect 包

其实反射的操作步骤非常的简单,就是通过实体对象获取反射对象(Value、Type),然后操作相应的方法即可。

下图描述了实例、Value、Type 三者之间的转换关系:

image-20210726180209819

# 1. 实例 → Value

func ValueOf(i interface {}) Value

# 2. 实例 → Type

func TypeOf(i interface{}) Type

# 3. Type → Value

Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。

reflect 包提供了两种方法,示例如下:

//New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型
func New(typ Type) Value
//Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址也不可改变
func Zero(typ Type) Value

如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的。例如:

func NewAt(typ Type, p unsafe.Pointer) Value

# 4. Value → Type

func (v Value) Type() Type

# 5. Value → 实例

Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转换。例如:

//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
//可以使用接口类型查询去还原为具体的类型
func (v Value) Interface() (i interface{})

//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64

# 6. Value 指针 → 值

从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下:

//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如果 v 类型是指针,则返回指针值的 Value,否则引起 panic
func (v Value) Elem() Value
//如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value

# 7. Type 指针 → 值

//t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic
//Elem 返回的是其内部元素的 Type
t.Elem() Type

# 8. 值 → Type 指针

//PtrTo 返回的是指向 t 的指针型 Type
func PtrTo(t Type) Type

# 9. Value 的值的可修改性

//通过 CanSet 判断是否能修改
func (v Value ) CanSet() bool
//通过 Set 进行修改
func (v Value ) Set(x Value)

Value 值在什么情况下可以修改?

我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法 reflect.ValueOf() 传进去的是一个值类型变量, 则获得的 Value 实际上是原对象的一个副本,这个 Value 是无论如何也不能被修改的。

根据 Go 官方关于反射的博客,反射有三大定律:

  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.

第一条是最基本的:反射可以从接口值得到反射对象。

  • 反射是一种检测存储在 interface 中的类型和值机制。这可以通过 TypeOf 函数和 ValueOf 函数得到。

第二条实际上和第一条是相反的机制,反射可以从反射对象获得接口值。

  • 它将 ValueOf 的返回值通过 Interface() 函数反向转变成 interface 变量。

前两条就是说 接口型变量反射类型对象 可以相互转化,反射类型对象实际上就是指的前面说的 reflect.Type 和 reflect.Value。

第三条:如果需要操作一个反射变量,则其值必须可以修改。

  • 反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。

# 三、Golang 反射获取接口变量信息

当执行 reflect.ValueOf(interface) 之后,就得到了一个类型为 “relfect.Value” 变量,可以通过它本身的 Interface() 方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型。不过,我们可能是已知原有类型,也有可能是未知原有类型,因此,下面分两种情况进行说明。

# 1. 已知原有类型

已知类型后转换为其对应的类型的做法如下,直接通过 Interface 方法然后强制转换,如下:

realValue := value.Interface().(已知的类型)

示例:

package main

import (
	"fmt"
	"reflect"
)

func main() {

	var num float64 = 1.2345

	pointer := reflect.ValueOf(&num)
	value := reflect.ValueOf(num)

	// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接 panic
	// Golang 对类型要求非常严格,类型一定要完全符合
	// 如下两个,一个是 *float64,一个是 float64,如果弄混,则会 panic
	convertPointer := pointer.Interface().(*float64)
	convertValue := value.Interface().(float64)

	fmt.Println(convertPointer)  //0xc000124008
	fmt.Println(convertValue)    //1.2345
}

说明:

  1. 转换的时候,如果转换的类型不完全符合,则直接panic,类型要求非常严格!
  2. 转换的时候,要区分是指针还是值
  3. 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”

# 2. 未知原有类型

很多情况下,我们可能并不知道其具体类型,那么这个时候,该如何做呢? 需要我们进行遍历探测其 Filed 来得知

示例:

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age int
	Sex string
}

func (p Person) Say(msg string)  {
	fmt.Println("hello,", msg)
}

func (p Person) PrintInfo()  {
	fmt.Println("姓名:%d,年龄:%d,性别:%s\n", p.Name, p.Age, p.Sex)
}

func main() {

	p1 := Person{"Hedon",18,"男"}

	DoFiledAndMethod(p1)
}

// 通过 interface{} 来获取任意类型参数
func DoFiledAndMethod(input interface{}) {

	// 先获取 input 的类型
	getType := reflect.TypeOf(input)
	fmt.Println("get Type is:", getType.Name())  //Person
	fmt.Println("get Kind is:", getType.Kind())  //struct

	// 再获取 input 的值
	getValue := reflect.ValueOf(input)
	fmt.Println("get all Fields is:", getValue) //{Hedon 18 男}

	// 获取字段
	// 1. 先获取 interface 的 reflect.Type,然后通过 NumField 进行遍历
	// 2. 再通过 reflect.Type 的 Field 获取其 Field
	// 3. 最后通过 Field 的 Interface() 得到对应的 value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface() //获取第 i 个值
		fmt.Printf("字段名称:%s,字段类型:%s,字段数值:%v\n", field.Name, field.Type, value)
	}

	// 操作方法
	// 1. 先获取 interface 的 reflect.Type,然后通过 .NumMethod 进行遍历
	// 2. 再通过 reflect.Type 的 Method 获取其 Method
	for i := 0; i < getType.NumMethod(); i++ {
		method := getType.Method(i)
		fmt.Printf("方法名称:%s,方法类型:%v\n", method.Name, method.Type)
	}
}

输出:

get Type is: Person
get Kind is: struct
get all Fields is: {Hedon 18 男}
字段名称:Name,字段类型:string,字段数值:Hedon
字段名称:Age,字段类型:int,字段数值:18
字段名称:Sex,字段类型:string,字段数值:男
方法名称:PrintInfo,方法类型:func(main.Person)
方法名称:Say,方法类型:func(main.Person, string)

说明:

通过运行结果可以得知获取未知类型的 interface 的具体变量及其类型的步骤为:

  1. 先获取 interface 的 reflect.Type,然后通过 NumField 进行遍历
  2. 再通过 reflect.Type 的 Field 获取其 Field
  3. 最后通过 Field 的 Interface() 得到对应的 value

通过运行结果可以得知获取未知类型的 interface 的所属方法(函数)的步骤为:

  1. 先获取 interface 的 reflect.Type,然后通过 NumMethod 进行遍历
  2. 再分别通过 reflect.Type 的 Method 获取对应的真实的方法(函数)
  3. 最后对结果取其 Name 和 Type 得知具体的方法名
  4. 也就是说反射可以将“反射类型对象”再重新转换为“接口类型变量”
  5. struct 或者 struct 的嵌套都是一样的判断处理方式

# 四、Golang 反射设置实际变量的值

reflect.Value 是通过 reflect.ValueOf(X) 获得的, 只有当 X 是指针的时候,才可以通过 reflec.Value 修改实际变量 X 的值,即:要修改反射类型的对象就一定要保证其值是 “addressable” 的。

也就是说:要想修改一个变量的值,那么必须通过该变量的指针地址。

通过 refPtrVal := reflect.Valueof( &var ) 的方式获取指针类型,然后使用 refPtrVal.Elem( ).Set(一个新的 reflect.Value) 来进行更改,传递给 Set() 的值也必须是一个 reflect.value

// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.

// Elem 返回接口 v 包含的值或者 v 指针指向的对象
// 如果 v 的类型不是 接口 或 Ptr,它会惊慌
// 如果 v 为 nil,则返回 0 值。
func (v Value) Elem() Value 

如果你的变量是一个指针、map、slice、channel、Array。那么你可以使用 reflect.Typeof(v).Elem() 来确定包含的类型。

示例:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345
	fmt.Println("num 的数值:", num)

	// 需要操作指针
	// 通过 reflect.ValueOf 获取 num 中的 reflect.Value,注意,参数必须是指针才能修改其值
	numPointer := reflect.ValueOf(&num)
	newValue := numPointer.Elem()
	fmt.Println("类型:", newValue.Type())
	fmt.Println("是否可以修改:", newValue.CanSet())

	// 重新赋值
	newValue.SetFloat(77)
	fmt.Println("新的数值:", num)

	// 如果reflect.ValueOf的参数不是指针,会如何?

	// 尝试直接修改
	//value := reflect.ValueOf(num)
	//value.SetFloat(6.28) //panic: reflect: reflect.Value.SetFloat using unaddressable value
	//fmt.Println(value.CanSet()) //false

	//pointer = reflect.ValueOf(num)
	//newValue = value.Elem() // 如果非指针,这里直接 panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}

输出:

num 的数值: 1.2345
类型: float64
是否可以修改: true
新的数值: 77

说明:

  1. 需要传入的参数是 *float64 这个指针,然后可以通过 pointer.Elem() 去获取所指向的 Value,注意一定要是指针
  2. 如果传入的参数不是指针,而是变量,那么
    • 通过 Elem 获取原始值对应的对象则直接 panic
    • 通过 CanSet 方法查询是否可以设置返回 false
  3. newValue.CantSet() 表示是否可以重新设置其值,如果输出的是 true 则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。
  4. reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的
  5. 也就是说如果要修改反射类型对象,其值必须是“addressable”(对应的要传入的是指针,同时要通过 Elem 方法获取原始值对应的反射对象)。
  6. struct 或者 struct 的嵌套都是一样的判断处理方式

尝试修改结构体中的字段数值:

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string
	Age int
	School string
}

func main()  {
	// 通过反射,来更改对象的值,前提是数值可以被更改
	s1 := Student{"Hedon", 18, "Wuhan University"}
	fmt.Printf("s1 类型:%T\n", s1)

	p1 := &s1
	fmt.Printf("p1 类型:%T\n", p1)
	fmt.Println("s1.Name:", s1.Name)
	fmt.Println("*p1.Name:", (*p1).Name)

	// 使用 reflect.ValueOf 取出值,注意传指针
	v1 := reflect.ValueOf(&s1)

	if v1.Kind() == reflect.Ptr {
		fmt.Println("v1.Elem 是否可以设置:", v1.Elem().CanSet())
		v1 = v1.Elem()
	}

	f1 := v1.FieldByName("Name")
	f1.SetString("Hedon Wang")
	f3 := v1.FieldByName("School")
	f3.SetString("Wuhan 幼儿园")
	fmt.Println("s1:", s1)
}

输出:

s1 类型:main.Student
p1 类型:*main.Student
s1.Name: Hedon
*p1.Name: Hedon
v1.Elem 是否可以设置: true
s1: {Hedon Wang 18 Wuhan 幼儿园}

# 五、Golang 反射进行方法调用

这算是一个高级用法了,前面我们只说到对类型、变量的几种反射的用法,包括如何获取其值、其类型、以及如何重新设置新值。但是在项目应用中,另外一个常用并且属于高级的用法,就是通过 reflect 来进行「方法/函数」的调用。比如我们要做框架工程的时候,需要可以随意扩展方法,或者说用户可以自定义方法,那么我们通过什么手段来扩展让用户能够自定义呢?关键点在于用户的自定义方法是未可知的,因此我们可以通过 reflect 来搞定。

这里涉及到 reflect 包中一个非常重要的方法:

// Call calls the function v with the input arguments in.
// For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]).
// Call panics if v's Kind is not Func.
// It returns the output results as Values.
// As in Go, each input argument must be assignable to the
// type of the function's corresponding input parameter.
// If v is a variadic function, Call creates the variadic slice parameter
// itself, copying in the corresponding values.

// 调用函数 v 时,输入参数为 in
// 例如,if len(in) == 3, v.Call(in) 表示 Go 调用 v(in[0], in[1], in[2])
// 如果 v 的 Kind 不是 Func,则 panic
// 它以 Value[] 返回输出结果
// 与 Go 一样,每个输入参数必须可赋值给函数对应的输入参数的类型
// 如果 v 是一个可变参数函数,则调用自己创建可变参数切片形参,复制相应的值
func (v Value) Call(in []Value) []Value {
	v.mustBe(Func)
	v.mustBeExported()
	return v.call("Call", in)
}

# 1. 通过反射,调用方法

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string
	Age int
	Sex string
}

func (p Person)Say(msg string)  {
	fmt.Println("hello,",msg)
}
func (p Person)PrintInfo()  {
	fmt.Printf("姓名:%s,年龄:%d,性别:%s\n",p.Name,p.Age,p.Sex)
}

func (p Person) Test(i,j int,s string){
	fmt.Println("Test:", i,j,s)
}

// 如何通过反射来进行方法的调用?
// 本来可以用结构体对象.方法名称()直接调用的,
// 但是如果要通过反射,
// 那么首先要将方法注册,也就是 MethodByName,然后通过反射调动 mv.Call
func main() {

	p1 := Person{"Hedon", 18, "Man"}

	//1. 要通过反射来调用起对应的方法,必须要先通过 reflect.ValueOf(interface) 来获取到reflect.Value
	//   得到“反射类型对象”后才能做下一步处理
	getValue := reflect.ValueOf(p1)

	// 2. 一定要指定参数为正确的方法名
	// 2.1 先看看没有参数的调用方法
	methodValue1 := getValue.MethodByName("PrintInfo")
	fmt.Printf("PrintInfo ---> Kind: %s, Type: %s\n", methodValue1.Kind(), methodValue1.Type())
	methodValue1.Call(nil) // 没有参数,直接写nil
	args1 := make([]reflect.Value, 0) //或者创建一个空的切片也可以
	methodValue1.Call(args1)

	// 2.2 有参数的方法调用
	methodValue2 := getValue.MethodByName("Say")
	fmt.Printf("Say ---> Kind : %s, Type : %s\n",methodValue2.Kind(),methodValue2.Type())
	args2 := []reflect.Value{reflect.ValueOf("反射机制")}
	methodValue2.Call(args2)

	methodValue3 := getValue.MethodByName("Test")
	fmt.Printf("Test ---> Kind : %s, Type : %s\n",methodValue3.Kind(),methodValue3.Type())
	args3 := []reflect.Value{reflect.ValueOf(100), reflect.ValueOf(200),reflect.ValueOf("Hello")}
	methodValue3.Call(args3)
}

输出:

PrintInfo ---> Kind: func, Type: func()
姓名:Hedon,年龄:18,性别:Man
姓名:Hedon,年龄:18,性别:Man
Say ---> Kind : func, Type : func(string)
hello, 反射机制
Test ---> Kind : func, Type : func(int, int, string)
Test: 100 200 Hello

# 2. 通过反射,调用函数

首先我们要先确认一点,函数像普通的变量一样,之前的章节中我们在讲到函数的本质的时候,是可以把函数作为一种变量类型的,而且是引用类型。如果说 Fun() 是一个函数,那么 f1 := Fun 也是可以的,那么 f1 也是一个函数,如果直接调用 f1(),那么运行的就是 Fun() 函数。

那么我们就先通过 ValueOf() 来获取函数的反射对象,可以判断它的 Kind,是一个 func,那么就可以执行 Call() 进行函数的调用。

示例:

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// 函数的反射
	f1 := fun1
	f1Value := reflect.ValueOf(f1)
	fmt.Printf("Kind : %s , Type : %s\n",f1Value.Kind(),f1Value.Type())

	f2value := reflect.ValueOf(fun2)
	fmt.Printf("Kind : %s , Type : %s\n",f2value.Kind(),f2value.Type())

	// 通过反射调用函数
	f1Value.Call(nil)
	f2value.Call([]reflect.Value{reflect.ValueOf(100), reflect.ValueOf("hello")})
}

func fun1()  {
	fmt.Println("我是函数 func1(),无参...")
}

func fun2(i int, s string)  {
	fmt.Println("我是函数 func2(),有参:", i, s)
}

输出:

Kind : func , Type : func()
Kind : func , Type : func(int, string)
我是函数 func1(),无参...
我是函数 func2(),有参: 100 hello

说明:

  1. 要通过反射来调用起对应的方法,必须要先通过 reflect.ValueOf(interface) 来获取到 reflect.Value,得到“反射类型对象”后才能做下一步处理。
  2. reflect.Value.MethodByName 这个 MethodByName,需要指定准确真实的方法名字,如果错误将直接 panic,MethodByName 返回一个函数值对应的 reflect.Value 方法的名字。
  3. []reflect.Value,这个是最终需要调用的方法的参数,可以没有或者一个或者多个,根据实际参数来定。
  4. reflect.Value 的 Call 这个方法,这个方法将最终调用真实的方法,参数务必保持一致,如果 reflect.Value.Kind 不是一个方法,那么将直接 panic。
  5. 本来可以用对象访问方法直接调用的,但是如果要通过反射,那么首先要将方法注册,也就是 MethodByName,然后通过反射调用 methodValue.Call。
上次更新: 7/27/2021, 6:41:10 PM