任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性。也可以用关键词替代, go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息 对于这个例子, 的类型信息是 ,数据信息是 ,这两部分信息都是存储...">

反射

1. 任意类型

在 Go 语言中,interface{} 常被称为“空接口”,它的确能承载<span style="background:#fff88f">任意类型的值,但这种“万能”特性背后存在明显的权衡和局限性interface{}也可以用any关键词替代,type any = interface{}

package main

import "log"

func main() {
    var anyItem interface{}
    // 等价于var anyItem any 
    anyItem = 42         // int
    anyItem = "hello"    // string
    anyItem = struct{}{} // 初始化空结构体
    log.Printf("%v", anyItem)
}

go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息

var a = 1 
var b interface{} = a

对于这个例子,b 的类型信息是 int,数据信息是 1,这两部分信息都是存储在 b 里面的。b 的内存结构
image.png
在上图中,b 的类型实际上是 eface,它是一个空接口,它的定义如下:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

也就是说,一个interface{}中实际上既包含了变量的类型信息,也包含了类型的数据。正因为如此,我们才可以通过反射来获取到变量的类型信息,以及变量的数据信息。

2. 反射

Go中的反射是用reflect包实现,reflect包实现了运行时的反射能力,能够让程序操作不同的对象。

Go中的反射是建立在类型系统之上,它与空接口interface{}密切相关。每个interface{}类型的变量包含一对值(type,value),type 表示变量的类型信息,value 表示变量的值信息。

[!info]
所以 nil!=nil,就可以理解了。

package main

import (
    "log"
)

func main() {
    var a any
    var b any
    log.Println(a, b)   //<nil> <nil>
    log.Println(a != b) // false
}

reflect有两个核心方法:

  • reflect.TypeOf() 获取类型信息,返回 Type 类型;
  • reflect.ValueOf() 获取数据信息,返回 Value 类型。
    阅读TypeOfValueOf 的源码会发现,这两个方法都接收一个 interface{} 类型的参数,然后返回一个 reflect.Typereflect.Value 类型的值。这也就是为什么我们可以通过 reflect.TypeOfreflect.ValueOf 来获取到一个变量的类型和值的原因。
    image.png

2.1. 反射定律

  1. 反射可以将 interface 类型变量转换成反射对象。
  2. 反射可以将反射对象还原成 interface 对象。
  3. 如果要修改反射对象,那么反射对象必须是可设置的(CanSet)。

2.1.1. 反射可以将 interface类型变量转换成反射对象

var a = 1
typeOfA := reflect.TypeOf(a)
valueOfA := reflect.ValueOf(a)

2.1.2. 反射可以将反射对象还原成 interface 对象

我们可以通过 reflect.Value.Interface 来获取到反射对象的 interface 对象,也就是传递给 reflect.ValueOf 的那个变量本身。 不过返回值类型是 interface{},所以我们需要进行类型断言。

var a = 1
valueOfA := reflect.ValueOf(a)
i := valueOfA.Interface()
fmt.Println(i.(int))   // 1

2.1.3. 如果要修改反射对象,那么反射对象必须是可设置的(CanSet

我们可以通过 reflect.Value.CanSet 来判断一个反射对象是否是可设置的。如果是可设置的,我们就可以通过 reflect.Value.Set 来修改反射对象的值。 这其实也是非常场景的使用反射的一个场景,通过反射来修改变量的值。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    v := reflect.ValueOf(&x)
    fmt.Println("settability of v:", v.CanSet())        // false
    fmt.Println("settability of v:", v.Elem().CanSet()) // true
}

反射对象是可设置的条件:

  • 反射对象是一个指针
  • 这个指针指向的是一个可设置的变量
    在我们传递一个值给 reflect.ValueOf 的时候,如果这个值只是一个普通的变量,那么 reflect.ValueOf会返回一个不可设置的反射对象。 因为这个值实际上被拷贝了一份,我们如果通过反射修改这个值,那么实际上是修改的这个拷贝的值,而不是原来的值。 所以go语言在这里做了一个限制,如果我们传递进 reflect.ValueOf 的变量是一个普通的变量,那么在我们设置反射对象的值的时候,会报错。 所以在上面这个例子中,我们传递了x的指针变量作为参数。这样,运行时就可以找到 x 本身,而不是 x 的拷贝,所以就可以修改 x 的值了。

但同时我们也注意到了,在上面这个例子中,v.CanSet() 返回的是 false,而 v.Elem().CanSet() 返回的是 true。 这是因为,v 是一个指针,而 v.Elem() 是指针指向的值,对于这个指针本身,我们修改它是没有意义的,我们可以设想一下, 如果我们修改了指针变量(也就是修改了指针变量指向的地址),那会发生什么呢?那样我们的指针变量就不是指向 x 了, 而是指向了其他的变量,这样就不符合我们的预期了。所以 v.CanSet() 返回的是 false。而 v.Elem().CanSet() 返回的是 true。这是因为 v.Elem() 才是 x 本身,通过 v.Elem() 修改 x 的值是没有问题的。
image.png

2.2. elem方法

reflect.Value 和 reflect.Type 这两个反射对象都有 Elem 方法,既然是不同的对象,那么它们的作用自然是不一样的。

2.2.1. reflect.Value 的 Elem 方法

reflect.ValueElem 方法的作用是获取指针指向的值,或者获取接口的动态值。也就是说,能调用 Elem 方法的反射对象,必须是一个指针或者一个接口。 在使用其他类型的 reflect.Value 来调用 Elem 方法的时候,会 panic:

package main

import (
    "log"
    "reflect"
)

func main() {
    var a = 1

    var b = &a
    var v reflect.Value = reflect.ValueOf(b)
    log.Printf("%v", v.Elem())   // 1
    log.Println("value is: ", v) // 10xc0000120d0

    // panic: reflect: call of reflect.Value.Elem on int Value
    reflect.ValueOf(a).Elem()
}

对于指针很好理解,其实作用类似解引用。而对于接口,还是要回到 interface 的结构本身,因为接口里包含了类型和数据本身,所以 Elem 方法就是获取接口的数据部分(也就是 iface 或 eface 中的 data 字段)。

指针类型:
image.png
接口类型:
image.png

2.2.2. reflect.Type的Elem方法

reflect.TypeElem 方法的作用是获取数组、chan、map、指针、切片关联元素的类型信息,也就是说,对于 reflect.Type 来说, 能调用 Elem 方法的反射对象,必须是数组、chan、map、指针、切片中的一种,其他类型的 reflect.Type 调用 Elem 方法会 panic

t1 := reflect.TypeOf([3]int{1, 2, 3}) // 数组 [3]int
fmt.Println(t1.String()) // [3]int
fmt.Println(t1.Elem().String()) // int

需要注意的是,如果我们要获取 map 类型 key 的类型信息,需要使用 Key 方法,而不是 Elem 方法。

m := make(map[string]string)
t1 := reflect.TypeOf(m)
fmt.Println(t1.Key().String()) // string

2.2.3. interface方法

这也是非常常用的一个方法,reflect.ValueInterface 方法的作用是获取反射对象的动态值。 也就是说,如果反射对象是一个指针,那么 Interface 方法会返回指针指向的值。

简单来说,如果 var i interface{} = x,那么 reflect.ValueOf(x).Interface() 就是 i 本身,只不过其类型是 interface{} 类型。可以通过reflect.ValueOf(x).Interface().(T)来断言获取实际类型的值。

2.3. kind方法

在go中,我们可以使用type关键词基于基本类型来定义各种新的类型,如:

// Kind 是 int
type myIny int
// Kind 是 Struct
type Person struct {
    Name string
    Age int
}

不管我们定义了多少种类型,在go看来都是下面的基本类型中的一个:

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
   Pointer
   Slice
   String
   Struct
   UnsafePointer
)

我们定义的类型在go的类型系统中都是基本类型的一种,这个基本类型就是 Kind。 也正因为如此,我们可以通过有限的 reflect.TypeKind 来进行类型判断。 也就是说,我们在通过反射来判断变量的类型的时候,只需要枚举 Kind 中的类型,然后通过 reflect.TypeKind 方法来判断即可。

func display(path string, v reflect.Value) {
   switch v.Kind() {
   case reflect.Invalid:
      fmt.Printf("%s = invalid\n", path)
   case reflect.Slice, reflect.Array:
      for i := 0; i < v.Len(); i++ {
         display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
      }
   case reflect.Struct:
      for i := 0; i < v.NumField(); i++ {
         fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
         display(fieldPath, v.Field(i))
      }
   case reflect.Map:
      for _, key := range v.MapKeys() {
         display(fmt.Sprintf("%s[%s]", path, formatAny(key)), v.MapIndex(key))
      }
   case reflect.Pointer:
      if v.IsNil() {
         fmt.Printf("%s = nil\n", path)
      } else {
         display(fmt.Sprintf("(*%s)", path), v.Elem())
      }
   case reflect.Interface:
      if v.IsNil() {
         fmt.Printf("%s = nil\n", path)
      } else {
         fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
         display(path+".value", v.Elem())
      }
   default:
      fmt.Printf("%s = %s\n", path, formatAny(v))
   }
}

2.4. addressable

go反射中最后一个很重要的话题是addressable。在go的反射系统中有两个关于寻址的方法:CanAddrCanSet
CanAddr方法的作用是判断反射对象是否可以寻址

  • 如果CanAddr返回 true,那么我们就可以通过 Addr 方法来获取反射对象的地址。
  • 如果CanAddr返回 false,那么我们就不能通过 Addr 方法来获取反射对象的地址。对于这种情况,我们就无法通过反射对象来修改变量的值。
    但是,CanAddr是true并不是说reflect.Value一定就能修改变量的值了。reflect.Value还有一个方法CanSet,只有CanSet返回 true,我们才能通过反射对象来修改变量的值。

2.5. 获取类型信息 - reflect.Type

reflect.Type 是一个接口,它代表了一个类型。我们可以通过 reflect.TypeOf 来获取一个类型的 reflect.Type 对象。 我们使用 reflect.Type 的目的通常是为了获取类型的信息,比如类型是什么、类型的名称、类型的字段、类型的方法等等。 又或者最常见的场景:结构体中的 jsontag,它是没有语义的,它的作用就是为了在序列化的时候,生成我们想要的字段名。 而这个 tag 就是需要通过反射来获取的。

reflect.Type 这个接口有很多方法,下面这些方法是所有的类型通用的方法:

// Type 是 Go 类型的表示。
//
// 并非所有方法都适用于所有类型。
// 在调用 kind 具体方法之前,先使用 Kind 方法找出类型的种类。因为调用一个方法如果类型不匹配会导致 panic
//
// Type 类型值是可以比较的,比如用 == 操作符。所以它可以用做 map 的 key
// 如果两个 Type 值代表相同的类型,那么它们一定是相等的。
type Type interface {
   // Align 返回该类型在内存中分配时,以字节数为单位的字节数
   Align() int
   
   // FieldAlign 返回该类型在结构中作为字段使用时,以字节数为单位的字节数
   FieldAlign() int
   
   // Method这个方法返回类型方法集中的第i个方法。
   // 如果 i 不在[0, NumMethod()]范围内,就会 panic。
   // 对于非接口类型 T 或 *T,返回的 Method 的 Type 和 Func 字段描述了一个函数,
   // 其第一个参数是接收者,并且只能访问导出的方法。
   // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。
   // 方法是按字典序顺序排列的。
   Method(int) Method

   // MethodByName 返回类型的方法集中具有该名称的方法和一个指示是否找到该方法的布尔值。
   // 对于非接口类型 T 或 *T,返回的 Method 的 Type 和 Func 字段描述了一个函数,
   // 其第一个参数是接收者。
   // 对于一个接口类型,返回的 Method 的 Type 字段给出的是方法签名,没有接收者,Func字段为nil。
   MethodByName(string) (Method, bool)

   // NumMethod 返回使用 Method 可以访问的方法数量。
   // 对于非接口类型,它返回导出方法的数量。
   // 对于接口类型,它返回导出和未导出方法的数量。
   NumMethod() int

   // Name 返回定义类型在其包中的类型名称。
   // 对于其他(未定义的)类型,它返回空字符串。
   Name() string

   // PkgPath 返回一个定义类型的包的路径,也就是导入路径,导入路径是唯一标识包的类型,如 "encoding/base64"。
   // 如果类型是预先声明的(string, error)或者没有定义(*T, struct{}, []int,或 A,其中 A 是一个非定义类型的别名),包的路径将是空字符串。
   PkgPath() string

   // Size 返回存储给定类型的值所需的字节数。它类似于 unsafe.Sizeof.
   Size() uintptr

   // String 返回该类型的字符串表示。
   // 字符串表示法可以使用缩短的包名。
   // (例如,使用 base64 而不是 "encoding/base64")并且它并不能保证类型之间是唯一的。如果是为了测试类型标识,应该直接比较类型 Type。
   String() string

   // Kind 返回该类型的具体种类。
   Kind() Kind

   // Implements 表示该类型是否实现了接口类型 u。
   Implements(u Type) bool

   // AssignableTo 表示该类型的值是否可以分配给类型 u。
   AssignableTo(u Type) bool

   // ConvertibleTo 表示该类型的值是否可转换为 u 类型。
   ConvertibleTo(u Type) bool

   // Comparable 表示该类型的值是否具有可比性。
   Comparable() bool
}

下面是某些类型特定的方法,对于这些方法,如果我们使用的类型不对,则会 panic

type Type interface {
   // Bits 以 bits 为单位返回类型的大小。
   // 如果类型的 Kind 不属于:sized 或者 unsized Int, Uint, Float, 或者 Complex,会 panic。
   Bits() int

   // ChanDir 返回一个通道类型的方向。
   // 如果类型的 Kind 不是 Chan,会 panic。
   ChanDir() ChanDir

   // IsVariadic 表示一个函数类型的最终输入参数是否为一个 "..." 可变参数。如果是,t.In(t.NumIn() - 1) 返回参数的隐式实际类型 []T.
   // 更具体的,如果 t 代表 func(x int, y ... float64),那么:
   // t.NumIn() == 2
   // t.In(0)是 "int" 的 reflect.Type 反射类型。
   // t.In(1)是 "[]float64" 的 reflect.Type 反射类型。
   // t.IsVariadic() == true
   // 如果类型的 Kind 不是 Func,IsVariadic 会 panic
   IsVariadic() bool

   // Elem 返回一个 type 的元素类型。
   // 如果类型的 Kind 不是 Array、Chan、Map、Ptr 或 Slice,就会 panic
   Elem() Type

   // Field 返回一个结构类型的第 i 个字段。
   // 如果类型的 Kind 不是 Struct,就会 panic。
   // 如果 i 不在 [0, NumField()) 范围内也会 panic。
   Field(i int) StructField

   // FieldByIndex 返回索引序列对应的嵌套字段。它相当于对每一个 index 调用 Field。
   // 如果类型的 Kind 不是 Struct,就会 panic。
   FieldByIndex(index []int) StructField

   // FieldByName 返回给定名称的结构字段和一个表示是否找到该字段的布尔值。
   FieldByName(name string) (StructField, bool)

   // FieldByNameFunc 返回一个能满足 match 函数的带有名称的 field 字段。布尔值表示是否找到。
   FieldByNameFunc(match func(string) bool) (StructField, bool)

   // In 返回函数类型的第 i 个输入参数的类型。
   // 如果类型的 Kind 不是 Func 类型会 panic。
   // 如果 i 不在 [0, NumIn()) 的范围内,会 panic。
   In(i int) Type

   // Key 返回一个 map 类型的 key 类型。
   // 如果类型的 Kind 不是 Map,会 panic。
   Key() Type

   // Len 返回一个数组类型的长度。
   // 如果类型的 Kind 不是 Array,会 panic。
   Len() int

   // NumField 返回一个结构类型的字段数目。
   // 如果类型的 Kind 不是 Struct,会 panic。
   NumField() int

   // NumIn 返回一个函数类型的输入参数数。
   // 如果类型的 Kind 不是Func.NumIn(),会 panic。
   NumIn() int

   // NumOut 返回一个函数类型的输出参数数。
   // 如果类型的 Kind 不是 Func.NumOut(),会 panic。
   NumOut() int

   // Out 返回一个函数类型的第 i 个输出参数的类型。
   // 如果类型的 Kind 不是 Func,会 panic。
   // 如果 i 不在 [0, NumOut()) 的范围内,会 panic。
   Out(i int) Type
}

2.5.1. 创建reflect.Type的方法

image.png

2.6. 获取值信息reflect.Value

reflect.Value 是一个结构体,它代表了一个值。 我们使用reflect.Value可以实现一些接收多种类型参数的函数,又或者可以让我们在运行时针对值的一些信息来进行修改。常常用在接收 interface{} 类型参数的方法中,因为参数是接口类型,所以我们可以通过 reflect.ValueOf 来获取到参数的值信息。 比如类型、大小、结构体字段、方法等等。

  • 设置值的方法:Set*SetSetBoolSetBytesSetCapSetComplexSetFloatSetIntSetLenSetMapIndexSetPointerSetStringSetUint。通过这类方法,我们可以修改反射值的内容,前提是这个反射值得是合适的类型。CanSet 返回 true 才能调用这类方法
  • 获取值的方法:InterfaceInterfaceDataBoolBytesComplexFloatIntStringUint。通过这类方法,我们可以获取反射值的内容。前提是这个反射值是合适的类型,比如我们不能通过 complex 反射值来调用 Int 方法(我们可以通过 Kind 来判断类型)。
  • map 类型的方法:MapIndexMapKeysMapRangeMapSet
  • chan 类型的方法:CloseRecvSendTryRecvTrySend
  • slice 类型的方法:LenCapIndexSliceSlice3
  • struct 类型的方法:NumFieldNumMethodFieldFieldByIndexFieldByNameFieldByNameFunc
  • 判断是否可以设置为某一类型:CanConvertCanComplexCanFloatCanIntCanInterfaceCanUint
  • 方法类型的方法:MethodMethodByNameCallCallSlice
  • 判断值是否有效:IsValid
  • 判断值是否是 nilIsNil
  • 判断值是否是零值:IsZero
  • 判断值能否容纳下某一类型的值:OverflowOverflowComplexOverflowFloatOverflowIntOverflowUint
  • 反射值指针相关的方法:AddrCanAddrtrue 才能调用)、UnsafeAddrPointerUnsafePointer
  • 获取类型信息:TypeKind
  • 获取指向元素的值:Elem
  • 类型转换:Convert

2.6.1. 创建reflect.Value的方式

image.png

3. 示例

3.1. 示例一

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 1.2345

    fmt.Println("==TypeOf==")
    t := reflect.TypeOf(x)
    fmt.Println("type: ", t)       // type:  float64
    fmt.Println("kind:", t.Kind()) // kind: float64

    fmt.Println("==ValueOf==")
    v := reflect.ValueOf(x)
    fmt.Println("value: ", v)                     // value:  1.2345
    fmt.Println("type:", v.Type())                // type:  float64
    fmt.Println("kind:", v.Kind())                // kind: float64
    fmt.Println("value:", v.Float())              //value: 1.2345
    fmt.Println(v.Interface())                    // 1.2345
    fmt.Printf("value is %5.2e\n", v.Interface()) // value is 1.23e+00

    y := v.Interface().(float64)
    fmt.Println(y) // 1.2345

    fmt.Println("===kind===")
    type MyInt int
    var m MyInt = 5
    v = reflect.ValueOf(m)
    fmt.Println("kind:", v.Kind()) // kind: int
    fmt.Println("type:", v.Type()) // type: main.MyInt
}

上面的例子,reflect 包中 reflect.TypeOf() 返回Type和 reflect.ValueOf() 返回Value类型 都有一个 Kind() 方法,Kind() 返回一个底层的数据类型,如 Unit,Float64,Slice, Int 等。

reflect.ValueOf() 返回的 Value 类型:

  • 它有一个 Type() 方法,返回的是 reflect.Value 的 Type
  • 它有获取 Value 类型值的方法
  • 如果我们知道是 float 类型,所以直接用 Float() 方法。
  • 如果不知道具体类型呢?由上面例子可知用 Interface() 方法,然后在进行类型断言 v.Interface().(float64) 来判断获取值

v.Kind() 和 v.Type() 区别:
上例中,在 type MyInt int 里,v.Kind() 与 v.Type() 返回了不同的类型值,Kind()返回的是 int,Type() 返回的是 MyInt。
在 Go 中,可以用type关键字定义自定义类型

  • Kind()方法返回底层类型。
  • 比如还有结构体,指针等类型用type定义的,那么 Kind() 方法就可以获取这些类型的底层类型。

3.2. 示例二

package main

import (
    "fmt"
    "reflect"
)

type student struct {
    Name string `json:"name"`
    Age  int    `json:"age" id:"1"`
}

func main() {
    stu := student{
        Name: "hangmeimei",
        Age:  15,
    }

    valueOfStu := reflect.ValueOf(stu)
    // 获取struct字段数量
    fmt.Println("NumFields: ", valueOfStu.NumField())
    // 获取字段 Name 的值
    fmt.Println("Name value: ", valueOfStu.Field(0).String(), ", ", valueOfStu.FieldByName("Name").String())
    // 字段类型
    fmt.Println("Name type: ", valueOfStu.Field(0).Type())

    typeOfStu := reflect.TypeOf(stu)
    for i := 0; i < typeOfStu.NumField(); i++ {
        // 获取字段名
        name := typeOfStu.Field(i).Name
        fmt.Println("Field Name: ", name)

        // 获取tag
        if fieldName, ok := typeOfStu.FieldByName(name); ok {
            tag := fieldName.Tag

            fmt.Println("tag-", tag, ", ", "json:", tag.Get("json"), ", id", tag.Get("id"))
        }
    }
}

输出如下:

NumFields:  2
Name value:  hangmeimei ,  hangmeimei
Name type:  string
Field Name:  Name
tag- json:"name" ,  json: name , id 
Field Name:  Age
tag- json:"age" id:"1" ,  json: age , id 1

获取struct信息的一些方法:

  • NumField() 获取结构体字段数量
  • Field(i) 可以通过 i 字段索引来获取结构体字段信息,比如 Field(i).Name 获取字段名
  • FieldByName(name) 通过name获取字段信息

3.3. 示例三

反射调用方法

package main

import (
    "log"
    "reflect"
)

type PersonBehavior interface {
    SayName()
    SayAge()
}

type Person struct {
    name string
    age  int16
}

func (p Person) SayName() {
    log.Println(p.name)
}

func (p Person) SayAge() {
    log.Println(p.age)
}

func main() {
    var p any = Person{
        name: "lisi",
        age:  12,
    }

    value := reflect.ValueOf(p)

    method := value.MethodByName("SayName")
    if method.IsValid() {
        method.Call(nil)
    }

    for i := range value.NumMethod() {
        method = value.Method(i)
        method.Call(nil)
    }
}

输出如下

2025/03/02 12:58:35 lisi
2025/03/02 12:58:35 12
2025/03/02 12:58:35 lisi

4. ref

https://www.cnblogs.com/jiujuan/p/17142703.html