Go 反射

目录

什么是反射

反射的弊端

reflect 包

Go 提供的反射方法

type Type 类型

type Kind 类型

TypeOf

ValueOf


什么是反射

​反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通过反射可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。大多数现代的高级语言都以各种形式支持反射功能,反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。

反射可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

反射的弊端

  • 代码难以阅读和维护。
  • 编译期间不能发现类型错误,有些bug只能在运行很长时间才能发现,可能造成不良后果。
  • 反射性能差,通常比正常代码慢一到两个数量级。在对性能要求高或大量反复调用的代码块里建议不要使用反射。

reflect 包

Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。

Go 提供的反射方法

GoLang reflect 反射官网

type Type 类型

type Type interface {

    // 在内存中分配时,返回此类型值的对齐方式(以字节为单位)
    Align() int

    // 当用作结构体中的字段时,返回此类型值的对齐方式(以字节为单位)。
    FieldAlign() int

    // 返回结构体中的第 i 个方法
    Method(i int) Method

    // 返回结构体中指定的方法,并返回是否找到该方法的bool值
    MethodByName(string) (Method, bool)

    // 返回可访问的方法数量
    NumMethod() int

    // 返回结构体名称
    Name() string

    // 返回包路径
    PkgPath() string

    // 返回类型存储所占用的直接大小
    Size() uintptr

    // 返回类型的字符串表示形式。字符串表示可以使用缩短的包名称,并且不能保证在类型之间是唯一的。要测试类型标识,请直接比较类型。
    String() string

    // 返回此类型的特定种类
    Kind() Kind

    // 判断是否实现了指定的接口u
    Implements(u Type) bool

    // 判断类型的值是否可分配给u类型
    AssignableTo(u Type) bool

    // 判断类型的值是否可转换为u类型,即使返回true,也可能会宕机,转换类型(切片)长度小于被转换类型的长度可能会宕机
    ConvertibleTo(u Type) bool

    // 判断此类型的值是否具有可比性。即使Comparable返回true,这种比较仍可能引发宕机。例如,接口类型的值是可比较的,但如果它们的动态类型不可比较,则比较会死机
    Comparable() bool

    // 返回类型的字节大小
    Bits() int

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

    // 判断函数输入类型
    IsVariadic() bool

    // 返回指针类型的数据类型。如果类型的Kind不是Array、Chan、Map、Pointer或Slice,则会引发宕机
    Elem() Type

    // 返回结构体种的第 i 个字段
    Field(i int) StructField

    // 返回与索引相对应的嵌套字段
    FieldByIndex(index []int) StructField

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

    // 以广度优先的顺序考虑结构本身中的字段,然后考虑任何嵌入结构中的字段。在最浅的嵌套深度处停止,嵌套深度包含一个或多个满足匹配函数的字段。如果该深度的多个字段满足匹配函数,则它们会相互抵消,FieldByNameFunc不会返回匹配。此行为反映了Go对包含嵌入字段的结构中的名称查找的处理
    FieldByNameFunc(match func(string) bool) (StructField, bool)

    // 返回函数类型的第i个输入参数的类型
    In(i int) Type

    // 返回映射类型的键类型。如果类型的Kind不是Map,会宕机
    Key() Type

    // 返回数组类型的长度
    Len() int

    // 返回结构类型的字段数量
    NumField() int

    // 返回函数类型的输入参数数量
    NumIn() int

    // 返回函数类型的输出参数数量
    NumOut() int

    // 返回函数类型的第i个输出参数的类型
    Out(i int) Type
}

type Kind 类型

Kind表示type所表示的特定类型。

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

TypeOf

func TypeOf(i any) Type 返回 i 的反射Type类型。如果 i 的值是nil接口,TypeOf返回nil。(返回Type类型后,返回值可以使用上面 type Type interface的方法)

示例1        reflect.TypeOf 的使用

func getType() {
	typeInt := reflect.TypeOf(1)
	fmt.Println(typeInt)          // 打印 int
	fmt.Println(typeInt.String()) // 打印 int
	fmt.Println(typeInt.Kind())   // 打印 int

	typeString := reflect.TypeOf("hello")
	fmt.Println(typeString)          // 打印 string
	fmt.Println(typeString.String()) // 打印 string
	fmt.Println(typeString.Kind())   // 打印 string
}

示例2        reflect.TypeOf 的使用

// 自定义一个数据类型
type User struct {
	UserName string `我是Tag`
	Age      int
}

func getType() {
	var u1 User
	typeUser1 := reflect.TypeOf(u1)
	fmt.Println(typeUser1)               // 打印 main.User
	fmt.Println(typeUser1.String())      // 打印 main.User
	fmt.Println(typeUser1.Kind())        // 打印 struct
	fmt.Println(typeUser1.Field(0).Name) // 打印 UserName

	var u2 = new(User)
	typeUser2 := reflect.TypeOf(u2)
	fmt.Println(typeUser2)          // 打印 *main.User
	fmt.Println(typeUser2.String()) // 打印 *main.User
	fmt.Println(typeUser2.Kind())   // 打印 ptr

	var u3 = new(User)
	typeUser3 := reflect.TypeOf(u3).Elem() // Elem():把指针类型转成普通类型
	fmt.Println(typeUser3)                 // 打印 main.User
	fmt.Println(typeUser3.String())        // 打印 main.User
	fmt.Println(typeUser3.Kind())          // 打印 struct
}

示例3        获取成员变量详细信息

type User struct {
	UserName string `我是Tag`
	Age      int
	student  Student
}

type Student struct {
	score float32
}

func getField() {
	typeUser := reflect.TypeOf(User{})
	// 获取成员变量详情
	numField := typeUser.NumField()
	for i := 0; i < numField; i++ {
		field := typeUser.Field(i)
		fmt.Println("成员变量详情:", field)
	}

	// 获取结构体中嵌套结构体student中的变量score
	subField := typeUser.FieldByIndex([]int{2, 0}) // []int{2,0} 2是student在User中的下标,0是score在Student中的下标
	fmt.Println(subField)
}

示例4        获取成员方法详细信息

type User struct {
	UserName string `我是Tag`
	Age      int
}

func (User) Insert() int {
	return 1
}

// 获取成员方法,不是指针方法,如果需要获取指针方法,需要reflect.TypeOf(&User{})取地址才可
func getMethod() {
	typeUser := reflect.TypeOf(User{})
	numMethod := typeUser.NumMethod()
	for i := 0; i < numMethod; i++ {
		method := typeUser.Method(i)
		fmt.Println("成员方法详情:", method)
	}
}

 示例5        获取方法入参出参详细信息

func Add(a, b int) int {
	return a + b
}

// 获取普通方法详情
func getFunc() {
	typeFunc := reflect.TypeOf(Add)
	fmt.Println("方法类型:", typeFunc.Kind())

	numIn := typeFunc.NumIn()
	fmt.Println("方法输入参数个数:", numIn)
	numOut := typeFunc.NumOut()
	fmt.Println("方法输出参数个数:", numOut)

	for i := 0; i < numIn; i++ {
		fmt.Printf("第 %d 个输入参数类型是 %s \n", i, typeFunc.In(i))
	}
	for i := 0; i < numOut; i++ {
		fmt.Printf("第 %d 个输出参数类型是 %s \n", i, typeFunc.Out(i))
	}
}

ValueOf

Go 反射_第1张图片

func ValueOf(i any) Value 返回一个新值,初始化为存储在接口中的具体值。ValueOf(nil)返回零。
func (v Value) Type() Type 返回v的Type类型。
func (v Value) Kind() Kind 返回v的类型。如果v是零值(IsValid返回false),Kind返回Invalid。
func (v Value) Addr() Value 返回一个表示v的地址的指针值 // 把普通类型转成指针类型。
func (v Value) CanAddr() bool 判断该值的地址是否可以通过Addr获取。这样的值称为可寻址值。如果值是切片的一个元素,可寻址数组的一个元素,可寻址结构的一个字段,或者指针解引用(elem)的结果,那么它就是可寻址的。如果CanAddr返回false,则调用Addr会出现panic。//是可寻址值的数据才能通过反射进行修改。
func (v Value) CanSet() bool 判断v的值是否可以更改。只有当Value是可寻址的并且不是通过使用未导出(未导出就是首字符小写的意思)的结构字段获得时,才能更改它。如果CanSet返回false,则调用Set或任何类型特定的setter(例如,SetBool、SetInt)将panic。
func (v Value) Elem() Value 返回接口v包含的值或指针v所指向的值。如果v的类型不是接口或指针,它会panic。如果v为nil,则返回0值。// 把指针类型转成普通类型。
func (v Value) Interface() (i any) 返回v的当前值作为interface{}。它相当于:var i interface{}
func (v Value) IsValid() bool 报告v是否代表一个值。如果v为零值,则返回false。// var i interface{};fmt.Println(reflect.ValueOf(i).IsValid()) 返回false
func (v Value) IsNil() bool 判断参数v是否为空。参数必须是chan、func、interface、map、pointer或slice值
func (v Value) IsZero() bool 判断v是否为其类型的零值。
func (v Value) Index(i int) Value 返回v的第i个元素,是可寻址值。如果v的类型不是数组、切片或字符串,或者i超出了范围,它就会panic。
func (v Value) Set(x Value) 将x赋值给v。如果CanSet返回false,它会panic。x的值Value类型,并且未导出的字段不能赋值。// 设置复合数据类型的时候使用
func (v Value) SetInt(x int64) 将v的底层值设置为x,如果v的Kind不是Int、Int8、Int16、Int32或Int64,或者CanSet()为false,则会panic。
func (v Value) SetString(x string) 将v的底层值设置为x。如果v的Kind不是String或CanSet()为false,则会panic。
func (v Value) FieldByName(name string) Value 返回名称为name的结构体字段。如果未找到字段,则返回零值。如果v的Kind不是结构体,它会panic。
func (v Value) Len() int 返回v的长度。如果v的Kind不是Array、Chan、Map、Slice、String或指向Array的指针,它会panic。
func (v Value) SetLen(n int) 将v的长度设置为n。如果v的Kind不是Slice,或者n为负数或大于Slice的cap,则会panic。
func (v Value) SetCap(n int) 将v的容量设置为n。如果v的Kind不是Slice,或者n小于Slice的len或大于Slice的原来的cap,则会panic。
func (v Value) MapIndex(key Value) Value 返回v中的键关联的值。如果v的Kind不是map,则会宕机。如果在映射中找不到键,或者如果v表示nil映射,则返回零值。与Go中一样,键的值必须可分配给映射的键类型。
func (v Value) SetMapIndex(key, elem Value) 将v中与key相关联的元素设置为elem。如果v的Kind不是Map,它会宕机。如果elem是零值,则SetMapIndex会从映射中删除该键。否则,如果v持有nil映射,则SetMapIndex将宕机。elem的键值对类型必须和v的Map键值对类型保持一致。
func (v Value) Send(x Value) 在通道v上发送x,阻塞的。如果v的Kind不是Chan,或者x的类型与v的元素类型不同,它会宕机。与Go一样,x的值必须是可分配给通道的元素类型(类型要一样)。
func (v Value) TrySend(x Value) bool 尝试在信道v上发送x,但不会阻塞。如果v的Kind不是Chan,它会宕机。它返回是否发送了值。与Go一样,x的值必须是可分配给通道的元素类型。
func (v Value) Recv() (x Value, ok bool) 从通道v接收并返回一个值。如果v的Kind不是Chan,它会宕机。接收将阻塞,直到值准备好为止。如果值x对应于通道上的发送,则布尔值ok为true,如果由于通道关闭而接收到零值,则为false。
func (v Value) TryRecv() (x Value, ok bool) 尝试从信道v接收值,但不会阻塞。如果v的Kind不是Chan,它会宕机。如果receive传递了一个值,那么x是传递的值,ok为true。如果接收不能在无阻塞的情况下完成,则x为零值,ok为false。如果通道是关闭的,则x是通道元素类型的零值,ok为false。
func (v Value) Call(in []Value) []Value 调用函数v。如果v的Kind不是Func,则会panic。它将输出结果作为Value切片返回。与Go一样,每个输入参数都必须可分配给函数对应输入参数的类型。如果v是一个可变函数,Call会创建可变分片参数,复制相应的值。
func (v Value) MethodByName(name string) Value 返回name对应的v中的方法。对返回函数调用的参数不应包括接收器;返回的函数将始终使用v作为接收器。如果没有找到任何方法,它将返回零值。
func New(typ Type) Value 返回一个值,该值表示指向指定类型的新零值的指针。也就是说,返回的Value的Type是PointerTo(典型值)// 就是创建一个对象的Kind类型。
func MakeSlice(typ Type, len, cap int) Value 创建一个指定类型、长度和容量的新的初始化为零的切片。
func MakeMap(typ Type) Value 创建指定类型的map。
func MakeMapWithSize(typ Type, n int) Value 创建一个n容量的指定类型的map。
func MakeChan(typ Type, buffer int) Value 创建一个指定类型和缓冲区大小的新通道。
更多API介绍,请查阅官网 https://golang.google.cn/pkg/reflect

示例1        reflect.ValueOf 的使用

func getValue() {
	intValue := reflect.ValueOf(1)
	stringValue := reflect.ValueOf("hello")
	userValue := reflect.ValueOf(User{})
	fmt.Println(intValue)    // 打印 1
	fmt.Println(stringValue) // 打印 hello
	fmt.Println(userValue)   // 打印 { 0}
}

示例2        Value 转 Type

func getValue() {
	intValue := reflect.ValueOf(1)
    // Value 转 Type
	intType := intValue.Type()	
	fmt.Println(intType)
}

示例3        指针结构体互相转换

func trans() {
	userValue := reflect.ValueOf(&User{})
	fmt.Println(userValue.Kind()) // 打印 ptr
	// 指针 转成 结构体
	userValuePtr := userValue.Elem()
	fmt.Println(userValuePtr.Kind()) // 打印 struct
	// 结构体 转成 指针
	userValue2 := userValuePtr.Addr()
	fmt.Println(userValue2.Kind()) // 打印 ptr
}

示例4        反射类型转普通类型

func trans() {
	iValue := reflect.ValueOf(1)
	// 方式一:把 反射类型转成普通类型
	iValue.Int()
	// 方式二:把 反射类型转成普通类型
	iValue2 := iValue.Interface().(int)
	fmt.Println(iValue2)

	// 把 反射类型 转成 结构体类型
	userType := reflect.ValueOf(User{})
	user := userType.Interface().(User)
	fmt.Println(user.UserName)
}

示例5        通过反射修改基础类型的值

func changeValue() {
	var i = 10
	iValue := reflect.ValueOf(&i)
	fmt.Println(iValue.Kind()) // 打印 ptr
	// 判断是否是可寻址值
	if iValue.CanAddr() {
		iValue.SetInt(20)
		fmt.Println("i = ", i)	// if 进不来,无打印
	}

	// 通过 elem 把指针类型解析成反射类型。注意:elem 只能被指针类型的反射所调用,所以在ValueOf的时候带&取址符号
	iValue2 := iValue.Elem()
	fmt.Println(iValue2.Kind()) // 打印 int
	// 判断是否是可寻址值
	if iValue2.CanAddr() {
		iValue2.SetInt(30)
		fmt.Println("i = ", i) // 打印 30
	}
}

示例6        通过反射修改结构体成员变量的值

type User struct {
	UserName string `我是Tag`
	Age      int
    gender   int    // 首字母小写是未导出字段,反射不能修改未导出字段
}

func changeValue() {
	var user = User{
		UserName: "张三",
		Age:      18,
	}
	fmt.Println("修改前的值:", user.UserName, user.Age) // 打印 张三 18

	userValue := reflect.ValueOf(&user).Elem()
	if userValue.CanAddr() {
		userValue.FieldByName("UserName").SetString("李四")
		userValue.FieldByName("Age").SetInt(28)
		fmt.Println("修改后的值:", user.UserName, user.Age) // 打印 李四 28
	}
}

示例7        通过反射修改嵌套结构体成员变量的值

type User struct {
	UserName string `我是Tag`
	Age      int
	gender   int
	Student  Student
}

type Student struct {
	Score float32
}

func changeValue() {
	var user = User{
		UserName: "张三",
		Student: Student{
			Score: 98,
		},
	}

	userValue := reflect.ValueOf(&user).Elem()
	if userValue.CanAddr() {
		studentValue := userValue.FieldByName("Student")
		fmt.Println(studentValue.Kind()) // 打印 struct
		// 修改 Score 的值
		studentValue.FieldByName("Score").SetFloat(59)
		fmt.Println(user.Student.Score) // 打印 59
	}
}

示例8        通过反射修改 slice 切片中的值

type User struct {
	UserName string `我是Tag`
	Age      int
    gender   int    // 首字母小写是未导出字段,反射不能修改未导出字段
}

func changeValue() {
	var userSlice = make([]*User, 3, 5)
	userSlice[0] = &User{
		UserName: "张三",
		Age:      18,
	}

	fmt.Println("修改前的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 张三 18

	sliceValue := reflect.ValueOf(userSlice)
	// 判断是否是可寻址的
	fmt.Println(sliceValue.CanAddr()) // 打印 false
	if sliceValue.Len() > 0 {
		// 获取切片中第0个元素
		sliceValue0 := sliceValue.Index(0)
		// 判断是否是可寻址的
		fmt.Println(sliceValue0.CanAddr()) // 打印 true
		// 查看是否是指针类型,如果是指针类型,需要 elem 解析
		fmt.Println(sliceValue0.Kind()) // 打印 ptr 是指针类型
		userValue := sliceValue0.Elem()
		userValue.FieldByName("UserName").SetString("李四")
		userValue.FieldByName("Age").SetInt(28)
		fmt.Println("修改后的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 李四 28
	}

	// 直接修改整个 user 对象
	sliceValue.Index(1).Set(reflect.ValueOf(&User{
		UserName: "王五",
		Age:      16,
	}))
	fmt.Println(userSlice[1].UserName, userSlice[1].Age) // 打印 王五 16
}

示例9        通过反射修改 map 中的值

func changeValue() {
	u1 := &User{
		UserName: "张三",
		Age:      18,
	}
	u2 := &User{
		UserName: "李四",
		Age:      20,
	}
	// 定义一个map对象
	userMap := make(map[int]*User, 5)
	userMap[0] = u1
	userMap[1] = u2

	// 反射value对象
	mapValue := reflect.ValueOf(userMap)
	// 把value类型转成type类型
	mapType := mapValue.Type()
	// 获取map中 key 的数据类型
	keyType := mapType.Key()
	fmt.Println("key的数据类型是:", keyType) // 打印 int
	// 获取map中 value 的数据类型。 注意这里的 elem 是 Type 的方法,作用是获取mapType的元素类型,不是解析指针
	valueType := mapType.Elem()
	fmt.Println("value的数据类型是:", valueType) //打印 *main.User

	// 修改 map中映射对应的值中的某一个成员变量的数据,比如只修改用户的年龄
	u1Value := mapValue.MapIndex(reflect.ValueOf(1))            // 修改map中key是1的数据
	fmt.Println("修改前的数据:", userMap[1].UserName, userMap[1].Age) // 打印  李四 20
	u1Value.Elem().FieldByName("Age").SetInt(28)
	fmt.Println("修改后的数据:", userMap[1].UserName, userMap[1].Age) // 打印  李四 28

	// 通过反射设置map的键值对
	k3 := 2
	u3 := &User{
		UserName: "王五",
		Age:      22,
	}
	mapValue.SetMapIndex(reflect.ValueOf(k3), reflect.ValueOf(u3))
	fmt.Println(userMap[k3].UserName, userMap[k3].Age) // 打印 王五 22
}

示例10        通过反射操作channel管道类型数据

func changeValue() {
	// 定义一个 chan 的数据
	var ch = make(chan int, 8)
	// 向 chan 中写入一行数据
	ch <- 10

	// 反射chan类型
	chanValue := reflect.ValueOf(&ch).Elem()

	if chanValue.CanAddr() {
		// 读取 chan 管道中的数据
		fmt.Println(chanValue.Recv())
	}

	// 向 chan 管道中写入数据
	chanValue.Send(reflect.ValueOf(20))

	// 获取把反射类型,转成普通类型
	c := chanValue.Interface().(chan int)
	fmt.Println(<-c)
}

示例11        通过反射调用普通方法

func callFunc() {
	valueFunc := reflect.ValueOf(Add)
	fmt.Println(valueFunc.Kind()) // 打印 func
	typeFunc := valueFunc.Type()
	numIn := typeFunc.NumIn() // 获取参数个数

	// 定义函数实参
	params := make([]reflect.Value, numIn)
	for i := 0; i < numIn; i++ {
		params[i] = reflect.ValueOf(1)
	}
	// 通过反射调用函数,返回切片Value,返回值中是函数返回结果
	callResult := valueFunc.Call(params)
	for i := 0; i < len(callResult); i++ {
		// 打印返回结果 // 实际开发中这里可以使用类型判断
		fmt.Println(callResult[i])
	}
}

示例12        通过反射调用结构体的成员方法

type User struct {
	UserName string `我是Tag`
	Age      int
	gender   int
	Student  Student
}

func (User) Insert(userName string) int {
	return 1
}

func callFunc() {
	user := &User{}
	userValue := reflect.ValueOf(user)
	// 通过方法名称获取结构体中的方法
	insertMethod := userValue.MethodByName("Insert")
	// 调用方法,返回函数结果
	callResult := insertMethod.Call([]reflect.Value{reflect.ValueOf("张三")})
	for i := 0; i < len(callResult); i++ {
		// 打印返回结果
		fmt.Println(callResult[i])
	}
}

示例13        通过反射创建结构体对象实例

func newStruct() {
	u := reflect.TypeOf(User{})
	// new一个对象的指针
	value := reflect.New(u)
	// 赋值
	value.Elem().FieldByName("UserName").SetString("张三")
	// 赋值
	value.Elem().FieldByName("Age").SetInt(18)

	// 反射类型转普通类型
	user := value.Interface().(*User)
	fmt.Println(user.UserName, user.Age) // 打印 张三 18
}

示例14        通过反射创建slice切片实例

func newSlice() {
	var slice []User
	sliceType := reflect.TypeOf(slice)
	// 创建一个指针类型的切片
	sliceValue := reflect.MakeSlice(sliceType, 3, 5)
	sliceValue.Index(0).Set(reflect.ValueOf(User{
		UserName: "张三",
		Age:      18,
	}))
	
	// 把反射的类型转成 普通类型
	users := sliceValue.Interface().([]User)
	for _, user := range users {
		fmt.Println(user.UserName, user.Age)	// 打印 张三 18,0,0
	}
}

示例15        通过反射创建map实例

func newMap() {
	var userMap map[int]*User
	mapType := reflect.TypeOf(userMap)
	// 创建map
	mapValue := reflect.MakeMap(mapType)
	//reflect.MakeMapWithSize(mapType,5)	// 指定容量

	// 给对象赋值
	u := &User{
		UserName: "张三",
		Age:      18,
	}
	mapValue.SetMapIndex(reflect.ValueOf(0), reflect.ValueOf(u))
	
	mp := mapValue.Interface().(map[int]*User)
	// 遍历对象
	for k, v := range mp {
		fmt.Printf("下标:%d, 数据:%s %d", k, v.UserName, v.Age)
	}
}

示例16        通过反射创建channel管道实例

func newChannel() {
	var ch chan User
	chanType := reflect.TypeOf(ch)
	// 创建 chan 对象
	chanValue := reflect.MakeChan(chanType, 5)
	u := User{
		UserName: "张三",
		Age:      18,
	}
	// 向 chan 中添加数据
	chanValue.Send(reflect.ValueOf(u))
	// 将反射类型转成普通类型
	c := chanValue.Interface().(chan User)
	fmt.Println(<-c)
}

全套教程地址:Golang全套教程

你可能感兴趣的:(Golang,golang)