目录

文章目录

  • 目录
  • 为什么需要反射?
  • reflect 包
    • 通过 reflect.TypeOf() 获取对象的反射类型 reflect.Type
    • 通过 reflect.Elem() 获取指针所指向的对象的反射类型
    • 通过 reflect.TypeOf() 获取结构体成员的反射类型
    • 通过 reflect.ValueOf 获取对象的反射值 reflect.Value
    • 通过反射访问结构体成员的值
    • 通过反射修改变量的值
      • 值可修改条件之:可被寻址
      • 值可修改条件之:可被导出(可被外部引用)
    • 通过类型信息创建实例
    • 通过反射调用函数
    • 通过反射调用方法
  • 应用实例:Struct Tags 与 Reflect

为什么需要反射?

强类型语言在编译期间会对对象(变量)进行类型、接口、字段(成员变量)、方法等合法性检测,检测不通过则无法完成编译。

反射技术则允许将需要调用的对象的信息检查工作从编译期间推迟到运行期间再现场执行。这样一来,就可以在编译期间先不明确目标对象的类型、接口、字段和方法,然后在运行期间根据目标对象自身的信息来决定如何处理。它还允许根据判断结果进行实例化新对象和相关方法的调用。是一种非常灵活的编程手段。

例如:有一个函数的形参是 Slice 类型,但是 Slice 里面的元素数据类型未知,如何来定义这个函数?

首先我们会想到基于 Golang Interface 实现的泛式编程,以此来支持多数据类型的,如果我们这么做:

func MethodTakeinSlice(in []interface{}){...}
...
slice := []int{1,2,3}
MethodTakeinSlice(slice)

就会得到一个错误:cannot use slice (type []int) as type []interface {} in argument to MethodTakeinSlice

因为,有别于 Empty interface{},特别的 Slice []interface{} 和 Slice []int 的类型会在编译阶段就被检测到了错误,如果不希望出错,则需要首先定义好 slice 实参的类型,例如:

func convert(in []AnyType) (out []interface{}) {out = make([]interface{}, len(in))for i, v := range in {out[i] = v}return
}

但是如此一来就失去了泛式编程的优势了,因为每一种特定类型都得做一个转换。

此时,就是应用 Golang reflect(反射)机制的场景了。反射机制是指在运行时态能够调用对象的方法和属性,主要用途就是使给定的程序能够动态地适应不同的运行情况

利用面向对象建模中的多态性也可以简化编写分别适用于多种不同情形的功能代码,但是反射可以解决多态性并不适用的更普遍情形,从而更大程度地避免硬编码(即把代码的细节 “写死”,缺乏灵活性)的代码风格。

使用 reflect 解决上面的问题:

package mainimport ("fmt""reflect"
)type Person struct {name stringage  int
}func Method(in interface{}) (ok bool) {v := reflect.ValueOf(in)if v.Kind() == reflect.Slice {ok = true}else {//panic}num := v.Len()for i := 0; i < num; i++ {fmt.Println(v.Index(i).Interface())}return ok
}func main() {s := []int{1, 3, 5, 7, 9}b := []float64{1.2, 3.4, 5.6, 7.8}Method(s)Method(b)
}

reflect 包

Golang 内建的 reflect 包实现了运行时反射。典型用法是用静态类型 interface{} 保存一个值,通过调用 TypeOf 函数获取其动态类型信息,该函数返回一个 Type 类型值。调用 ValueOf 函数返回一个 Value 类型值,代表运行时的数据。Zero 接受一个 Type 类型参数并返回一个代表该类型零值的 Value 类型值。

Go 程序的反射机制无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息,常见的就是 Struct Tags。

通过 reflect.TypeOf() 获取对象的反射类型 reflect.Type

使用 reflect.TypeOf() 函数可以获得任意对象的 reflect.Type(反射类型对象),程序通过 reflect.Type 可以访问对象的类型信息。

示例:

package mainimport ("fmt""reflect"
)type Enum intconst (Zero Enum = 0
)type Student struct {Name stringAge  int
}func main() {var stu StudenttypeOfStu := reflect.TypeOf(stu)fmt.Println("类型名称: ", typeOfStu.Name())fmt.Println("种类: ", typeOfStu.Kind())typeOfZero := reflect.TypeOf(Zero)fmt.Println("类型名称: ", typeOfZero.Name())fmt.Println("种类: ", typeOfZero.Kind())}

结果:

$ go run tst.go
类型名称:  Student
种类:  struct
类型名称:  Enum
种类:  int

需要注意的是,这里的 Kind(种类)和 Type(类型)是两个概念。反射种类(Kind)的定义:Go 程序中的 Type 指的是系统原生数据类型,如:int、string、bool、float32 等,以及使用 type 关键字自定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。而 Kind 指的是对象归属的品种。

编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。

通过 reflect.Elem() 获取指针所指向的对象的反射类型

通过 reflect.Elem() 方法获取指针指向的元素类型。这个获取过程被称为 “取元素”,等效于对指针类型变量执行了一次 * 操作。

package mainimport ("fmt""reflect"
)type Student struct {Name stringAge  int
}func main() {var stu = &Student{Name: "kitty", Age: 20}typeOfStu := reflect.TypeOf(stu)fmt.Printf("name: '%v', kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())typeOfStu = typeOfStu.Elem()fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
}

结果:

$ go run tst.go
name: '', kind: 'ptr'
element name: 'Student', element kind: 'struct'

注意:指针变量的类型名称是空,不是 *Student。

通过 reflect.TypeOf() 获取结构体成员的反射类型

任意对象通过 reflect.TypeOf() 获得反射对象的类型信息后,如果它的类型是结构体,可以通过 reflect.Type(反射类型对象)的 NumField() 和 Field() 方法获得结构体成员的详细信息。

结构体成员访问的方法列表:

其中,Field() 方法返回 StructField 结构,用于描述目标结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如:偏移、索引、是否为匿名字段、结构体标签(Struct Tags)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。StructField 的声明如下:

type StructField struct {Name      string     // 字段名PkgPath   string     // 字段路径Type      Type       // 字段反射类型对象Tag       StructTag  // 字段的结构体标签Offset    uintptr    // 字段在结构体中的相对偏移Index     []int      // Type.FieldByIndex 中的返回的索引值Anonymous bool       // 是否为匿名字段
}

示例:下面代码中实例化一个结构体并遍历其成员,再通过 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。

package mainimport ("fmt""reflect"
)func main() {type cat struct {Name stringType int `json:"type" id:"100"`}ins := cat{Name: "mimi", Type: 1}typeOfCat := reflect.TypeOf(ins)for i := 0; i < typeOfCat.NumField(); i++ {fieldType := typeOfCat.Field(i)fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)}if catType, ok := typeOfCat.FieldByName("Type"); ok {fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))}
}

结果:

$ go run tst.go
name: Name  tag: ''
name: Type  tag: 'json:"type" id:"100"'
type 100

通过 reflect.ValueOf 获取对象的反射值 reflect.Value

反射不仅可以获取任意对象的类型信息,还可以动态地获取或者设置对象(变量)的值,使用 reflect.Value 获取和设置变量的值。变量、interface{} 和 reflect.Value 是可以相互转换的。这点在实际开发中,会经常碰到。

使用 reflect.ValueOf() 函数获得对象的 reflect.Value(反射值对象)。书写格式如下:

rValue := reflect.ValueOf(rawValue)

返回包含有 rawValue 的值信息。reflect.Value 与原值间可以通过 “值包装” 和 “值获取” 互相转化。reflect.Value 是一些反射操作的重要类型,如:反射调用函数。

通过 reflect.Value 重新获得原始值,可以通过下面几种方法从 reflect.Value 中获取原值。


示例:下面代码中,将整型变量中的值使用 reflect.Value 获取反射值对象。再通过 reflect.Value 的 Interface() 方法获得 interface{} 类型的原值,通过 int 类型对应的 reflect.Value 的 Int() 方法获得整型值。

package mainimport ("fmt""reflect"
)func main() {var a int = 1024valueOfA := reflect.ValueOf(a)var getA int = valueOfA.Interface().(int)var getB int = int(valueOfA.Int())fmt.Println(getA, getB)
}

结果:

$ go run tst.go
1024 1024

其中,将 valueOfA 反射值对象以 interface{} 类型取出,通过类型断言转换为 int 类型并赋值给 getA。

通过反射访问结构体成员的值

反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意成员的值的访问。

示例:下面代码构造一个结构体包含不同类型的成员。通过 reflect.Value 提供的成员访问函数,可以获得结构体值的各种数据。

package mainimport ("fmt""reflect"
)type Student struct {Name stringAge  int// 嵌入字段float32boolnext *Student
}func main() {rValue := reflect.ValueOf(Student{next: &Student{},})fmt.Println("NumField:", rValue.NumField())floatField := rValue.Field(2)fmt.Println("Field:", floatField.Type())fmt.Println("FieldByName(\"Age\").Type:", rValue.FieldByName("Age").Type())fmt.Println("FieldByIndex([]int{4, 0}).Type():", rValue.FieldByIndex([]int{4, 1}).Type())
}

结果:

$ go run tst.go
NumField: 5
Field: float32
FieldByName("Age").Type: int
FieldByIndex([]int{4, 0}).Type(): int

注:[]int{4,1} 中的 4 表示,在 Student 结构中索引值为 4 的成员,也就是 next。next 的类型为 Student,也是一个结构体,因此使用 []int{4,1} 中的 1 继续在 next 值的基础上索引,结构为 Student 中索引值为 1 的 Age 字段,类型为 int。

注意,当我们通过反射去访问一个变量的值时,我们需要判断反射值是否为空和有效性。反射值对象(reflect.Value)提供一系列方法进行零值和空判定。

  • IsNil() 常被用于判断指针是否为空;
  • IsValid() 常被用于判定返回值是否有效。

下面的例子将会对各种方式的空指针进行 IsNil() 和 IsValid() 的返回值判定检测。同时对结构体成员及方法查找 map 键值对的返回值进行 IsValid() 判定。

package mainimport ("fmt""reflect"
)func main() {// *int 类型空指针变量var a *intfmt.Println("var a *int: ", reflect.ValueOf(a).IsNil())// nil 值fmt.Println("nil: ", reflect.ValueOf(nil).IsValid())// *int 类型空指针fmt.Println("(*int)(nil): ", reflect.ValueOf((*int)(nil)).Elem().IsValid())s := struct{}{}// 尝试从结构体中查找一个不存在的字段fmt.Println("不存在的结构体成员: ", reflect.ValueOf(s).FieldByName("").IsValid())// 尝试从结构体中查找一个不存在的方法fmt.Println("不存在的方法: ", reflect.ValueOf(s).MethodByName("").IsValid())// 实例化一个 mapm := map[int]int{}// 尝试从 map 中查找一个不存在的键fmt.Println("不存在的键: ", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

结果:

$ go run tst.go
var a *int:  true
nil:  false
(*int)(nil):  false
不存在的结构体成员:  false
不存在的方法:  false
不存在的键:  false

注:MapIndex() 方法能根据给定的 reflect.Value 类型的值查找 Map,并且返回查找到的结果。

通过反射修改变量的值

使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。

  • 使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。

  • 使用 reflect.Value 修改值的相关方法如下表所示。

以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。在已知值的类型时,应尽量使用值对应类型的反射设置值。

值的修改从表面意义上叫可寻址,换一种说法就是值必须 “可被设置”。那么,想修改变量值,一般的步骤是:

  1. 取这个变量的地址或者这个变量所在的结构体已经是指针类型。
  2. 使用 reflect.ValueOf 进行值包装。
  3. 通过 Value.Elem() 获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用 set 设置时会报出宕机错误。
  4. 使用 Value.SetXXX 设置值。

值可修改条件之:可被寻址

可以被寻址,简单地说就是这个变量必须能被修改。示例代码如下:

package mainimport "reflect"func main() {var a int = 1024rValue := reflect.ValueOf(a)// 尝试将 a 修改为 1 (此处会崩溃)rValue.SetInt(1)
}

错误信息:

panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

程序崩溃的原因是:SetInt正在使用一个不能被寻址的值。

从 reflect.ValueOf 传入的是 a 的值,而不是 a 的地址,这个 reflect.Value 当然是不能被寻址的。将代码修改一下:

package mainimport ("fmt""reflect"
)func main() {var a int = 1024rValue := reflect.ValueOf(&a)// 取出 &a 地址的元素 (a 的值)rValue = rValue.Elem()rValue.SetInt(1)fmt.Println(rValue.Int())
}

注:使用 reflect.Value 类型的 Elem() 方法获取 a 地址的元素,也就是 a 的值。reflect.Value 的 Elem() 方法返回的值类型也是 reflect.Value。此时 rValue 表示的是 a 的值且可以寻址。使用 SetInt() 方法设置值时不再发生崩溃。

另外,当 reflect.Value 不可寻址时,使用 Addr() 方法也是无法取到值的地址的,同时会发生宕机。虽然说 reflect.Value 的 Addr() 方法类似于语言层的&操作;Elem() 方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。

值可修改条件之:可被导出(可被外部引用)

结构体成员中,如果字段(Field)可被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:

package mainimport "reflect"func main() {type dog struct {legCount int}valueOfDog := reflect.ValueOf(&dog{})valueOfDog = valueOfDog.Elem()vLegCount := valueOfDog.FieldByName("legCount")// 尝试设置 legCount 的值 (这里会发生崩溃)vLegCount.SetInt(4)
}

错误信息:

panic: reflect: reflect.flag.mustBeAssignable using value obtained using unexported field

原因是:SetInt() 使用的值来自于一个未导出的字段。

为了能修改这个值,需要将该字段导出。将 dog 中的 legCount 的成员首字母大写,导出 LegCount 让反射可以访问,修改后的代码如下:

package mainimport ("fmt""reflect"
)func main() {type dog struct {LegCount int}valueOfDog := reflect.ValueOf(&dog{})valueOfDog = valueOfDog.Elem()vLegCount := valueOfDog.FieldByName("LegCount")vLegCount.SetInt(4)fmt.Println(vLegCount.Int())
}

所以在 json 包中,对结构体成员名为小写的字段不进行处理。

通过类型信息创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。例如 reflect.Type 的类型为 int 时,创建 int 的指针,即 *int,代码如下:

package mainimport ("fmt""reflect"
)func main() {var a inttypeOfA := reflect.TypeOf(a)// 根据反射类型对象创建类型实例aIns := reflect.New(typeOfA)fmt.Println(aIns.Type(), aIns.Kind())
}

结果:

$ go run tst.go
*int ptr

注:使用 reflect.New() 函数传入变量 a 的反射类型对象,创建这个类型的实例值,值以 reflect.Value 类型返回。这步操作等效于:new(int),因此返回的是 *int 类型的实例。

通过反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。

下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。

package mainimport ("fmt""reflect"
)func add(a, b int) int {return a + b
}func main() {// 将函数包装为反射值对象funcValue := reflect.ValueOf(add)// 构造函数参数,传入两个整形值paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}// 反射调用函数retList := funcValue.Call(paramList)fmt.Println(retList[0].Int())
}

反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用

通过反射调用方法

调用方法和调用函数是一样的,只不过结构体需要先通过 rValue.Method() 先获取方法再调用。

package mainimport ("fmt""reflect"
)type MyMath struct {Pi float64
}func (myMath MyMath) Sum(a, b int) int {return a + b
}func (myMath MyMath) Dec(a, b int) int {return a - b
}func main() {var myMath = MyMath{Pi: 3.14159}// 获取 myMath 的值对象rValue := reflect.ValueOf(myMath)// 获取到该结构体有多少个方法//numOfMethod := rValue.NumMethod()// 构造函数参数,传入两个整形值paramList := []reflect.Value{reflect.ValueOf(30), reflect.ValueOf(20)}// 调用结构体的第一个方法 Method(0)result := rValue.Method(0).Call(paramList)fmt.Println(result[0].Int())}

注:在反射值对象中方法索引的顺序并不是结构体方法定义的先后顺序,而是根据方法的 ASCII 码值来从小到大排序,所以 Dec 排在第一个,也就是 Method(0)。

应用实例:Struct Tags 与 Reflect

Struct Tags 实现了 Golang 的反射(Reflect)机制,通过 Tags 将 Struct 的信息记录下来,并提供 reflect 包来调用这些接口,使得在程序运行的过程中,程序自身可以通过这些接口(reflect 包)来获得这些信息并实现预期的逻辑。例如:在需要将 Struct 转换为其它数据格式时,会根据 Tags 中定义的信息进行转换。

Struct Tags 类似注释,使用反引号 “`”,Golang 提供了 reflect 包来对 Struct Tags 进行解析。

示例:

package mainimport ("fmt""reflect"
)type employee struct {ID       int     `json:"id"`Name     string  `json:"名字" validate:"presence,min=2,max=40"`Age      int     `json:"年龄"`Desc     string  `json:"描述" back:"好看否"`weight   float64 `json:"weight" 单位:"kg"`Salary   float64 `json:"-"`Email    string  `validate:"email,required"`MateName string  `json:"mate_name,omitempty"`
}func main() {zhangsan := employee{ID:       1,Name:     "张三",Age:      18,Desc:     "秀色可餐",weight:   48.0,Salary:   12.0,MateName: "Prince",}t := reflect.TypeOf(zhangsan)fmt.Println("Type: ", t.Name())fmt.Println("Kind: ", t.Kind())fmt.Println("Num: ", t.NumField())tagName := "validate"for i := 0; i < t.NumField(); i++ {field := t.Field(i)tag := field.Tag.Get(tagName)fmt.Printf("%d. %v(%v), tag:'%v'\n", i+1, field.Name, field.Type.Name(), tag)}
}

结果:

Type:  employee
Kind:  struct
Num:  8
1. ID(int), tag:''
2. Name(string), tag:'presence,min=2,max=40'
3. Age(int), tag:''
4. Desc(string), tag:''
5. weight(float64), tag:''
6. Salary(float64), tag:''
7. Email(string), tag:'email,required'
8. MateName(string), tag:''

通过 reflect 包,程序能够获取结构体的基本信息,包括它的成员清单、成员名称和成员类型。调用 field.Tag.Get 方法可以返回与 tagName 名匹配的 Struct Tags 的字符串,基于此我们就可以自由地去实现想要的逻辑了。

例如 json.Marshal 方法就有使用到反射机制,来完成 Struct 和 JSON 之间的数据格式转换。

package mainimport ("encoding/json""fmt""reflect"
)type employee struct {ID       int     `json:"id"`Name     string  `json:"名字" validate:"presence,min=2,max=40"`Age      int     `json:"年龄"`Desc     string  `json:"描述" back:"好看否"`weight   float64 `json:"weight" 单位:"kg"`Salary   float64 `json:"-"`Email    string  `validate:"email,required"`MateName string  `json:"mate_name,omitempty"`
}func main() {zhangsan := employee{ID:       1,Name:     "张三",Age:      18,Desc:     "秀色可餐",weight:   48.0,Salary:   12.0,MateName: "Prince",}fmt.Println(zhangsan)re, _ := json.Marshal(zhangsan)fmt.Println(string(re))
}

结果:

$ go run tst.go
{1 张三 18 秀色可餐 48 12  Prince}
{"id":1,"名字":"张三","年龄":18,"描述":"秀色可餐","Email":"","mate_name":"Prince"}

其中,有以下要点需要注意

  1. 如果结构体成员名称首字母为小写时,则不进行转换。例如:weight。
  2. 如果 Tag 中定义了 json:"XXX",则 XXX 作为 JSON key。
  3. 如果没有使用 Tag json:"XXX",则 JSON key 和 Struct 成员名保持一致。
  4. 如果 Tag 中定义了 json:"-",则不进行转换。
  5. 如果 Tag 中定义了 json:",omitempty",则表示当成员数值为空时,可直接忽略。

可见,在 json 包中,Struct Tag json:"XXX" 是用来指导 json.Marshal/Unmarshal 的。

此外,我们还可以通过 Struct Tags 中的其他 Tag 类型来实现别的功能,例如:限定成员 Age 的值在 1-100 之间。

package mainimport ("fmt""reflect""strconv""strings"
)type Person struct {Name string `json:"name"`Age  int    `json:"age" valid:"1-100"`
}func (p *Person) validation() bool {v := reflect.ValueOf(*p)tag := v.Type().Field(1).Tag.Get("valid")val := v.Field(1).Interface().(int)fmt.Printf("tag=%v, value=%v\n", tag, val)result := strings.Split(tag, "-")var min, max intmin, _ = strconv.Atoi(result[0])max, _ = strconv.Atoi(result[1])if val >= min && val <= max {return true}return false
}func main() {person1 := Person{"tom", 12}if person1.validation() {fmt.Printf("person 1: valid\n")} else {fmt.Printf("person 1: invalid\n")}person2 := Person{"tom", 250}if person2.validation() {fmt.Printf("person 2 valid\n")} else {fmt.Printf("person 2 invalid\n")}
}

结果:

$ go run tst.go
tag=1-100, value=12
person 1: valid
tag=1-100, value=250
person 2 invalid

上述例子我们给 Person 实现了一个 validate 方法,用于验证 Age 的值是否合理。我们再将这个函数扩展至验证任意结构体的 Age 成员。

package mainimport ("fmt""reflect""strconv""strings"
)type Person struct {Name string `json:"name"`Age  int    `json:"age" valid:"1-100"`
}func validateStruct(s interface{}) bool {v := reflect.ValueOf(s)for i := 0; i < v.NumField(); i++ {fieldTag := v.Type().Field(i).Tag.Get("valid")fieldName := v.Type().Field(i).NamefieldType := v.Field(i).Type()fieldValue := v.Field(i).Interface()if fieldTag == "" || fieldTag == "-" {continue}if fieldName == "Age" && fieldType.String() == "int" {val := fieldValue.(int)tmp := strings.Split(fieldTag, "-")var min, max intmin, _ = strconv.Atoi(tmp[0])max, _ = strconv.Atoi(tmp[1])if val >= min && val <= max {return true}return false}}return true
}func main() {person1 := Person{"tom", 12}if validateStruct(person1) {fmt.Printf("person 1: valid\n")} else {fmt.Printf("person 1: invalid\n")}person2 := Person{"tom", 250}if validateStruct(person2) {fmt.Printf("person 2: valid\n")} else {fmt.Printf("person 2: invalid\n")}
}

结果:

$ go run tst.go
person 1: valid
person 2: invalid

上述例子中的 validateStruct 函数可以接收任意类型的变量作为参数(interface{} 作为所有类型的 “基类”)并进行校验。

Go 语言编程 — reflect 反射机制相关推荐

  1. Java 语言基础(异常机制和File类,IO流,多线程,网络编程,反射机制)

    原文:Java 语言基础(异常机制和File类,IO流,多线程,网络编程,反射机制) 异常机制和File类 异常机制 基本概念 异常就是"不正常"的含义,在 Java 语言中主要指 ...

  2. JAVA语言中的反射机制

    在Java 运行时 环境中,对于任意一个类,能否知道这个类有哪些属性和方法?     对于任意一个对象,能否调用他的方法?这些答案是肯定的,这种动态获取类的信息,以及动态调用类的方法的功能来源于JAV ...

  3. Java —— Reflect反射机制

    JAVA反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制. ...

  4. Go语言类库-reflect(反射)

    概述 什么是反射? 反射是计算机程序在运行时可以访问,检查和修改本身状态或者行为的一种能力,大多数编程语言都支持反射.Go语言中,使用反射可以在程序执行过程中更新变量和检查对象的属性,调用对象的方法. ...

  5. Go 语言编程 — 高级数据类型 — 结构体

    目录 文章目录 目录 结构体 访问结构体成员 向函数传递结构体 结构体指针 结构体标签(Struct Tag) 结构体 Golang 中,结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.与 ...

  6. 直白点理解c 语言中的循环体,反射机制 小小谈 - osc_nnbkiac5的个人空间 - OSCHINA - 中文开源技术交流社区...

    反射机制(Reflection) [TOC] 写在前面 本文地址:https://www.cnblogs.com/oberon-zjt0806/p/11082012.html 这里是Oberon 本文 ...

  7. Java基础——Java反射机制及IoC原理

    一.概念 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义.在java中,只要给定类的名字, 那么就可以通过反射机制 ...

  8. Java基础-反射机制

    目录 1.反射机制概述 2.获取class的三种方式 2.1.Class.forName()方式 2.2.obj.getClass()方式 2.3..class方式 3.通过反射实例化对象 4.通过读 ...

  9. Java反射机制(反射Field,Method,Constructor,资源绑定器)

    目录 反射机制 1.反射机制的作用 2.反射机制的相关类在哪个包下 3.反射机制的相关类有哪些 获取Class的三种方式 通过反射实例化对象 通过读属性文件实例化对象 只让静态代码块执行可以使用for ...

最新文章

  1. 企业根CA方法客户机证书的解决方案,ISA2006系列之三十
  2. 分布式任务队列 Celery — 实践
  3. [Python] Django框架入门
  4. 互联网1分钟 |1128
  5. 5种IO模式形象的比喻
  6. [转]数据库分库分表
  7. LeetCode 1171. 从链表中删去总和值为零的连续节点(哈希表)
  8. repadmin查看域控之间的复制状态
  9. 主干开发前要知道的,4错误认识+3优势
  10. Cissp全过程(简介到考试后)
  11. css中背景的应用及BFC与IFC应用
  12. 使用Scylla进行OSINT信息收集
  13. 哈夫曼树的构造和哈夫曼编码实现详细讲解(含例题详细讲解)
  14. qlistview 自定义控件_qlistview使用自定义模型的复选框
  15. su - root 切换失败
  16. java ee 7精粹_Java EE 7精粹 ([美]Arun Gupta) 中文pdf扫描版[68MB]
  17. 图片1920x1080分辨率怎么调 ?图片如何修改分辨率?
  18. 【简欧风格设计装修案例】华丽而又不失优雅,时尚优雅并存!
  19. 用C/C++写一个简单的音乐播放器(基于windows控制台编程)
  20. mysql的information_schema数据库

热门文章

  1. 网页图表Highcharts实践教程之外层图表区
  2. Mac OS X 10.9.3 Beta 8升级教程
  3. dabs是什么意思_单词flounder是什么中文意思
  4. element ui input视图没刷新_聊聊前端 UI 组件:组件体系
  5. android 触摸 卡顿,Android CoordinatorLayout(五) 严重的卡顿BUG
  6. 水磨石地面分隔条设置示意图_水磨石抛光过程中什么时候用百洁垫?什么时候用百亮钢丝棉?...
  7. Nilearn中的基本操作和查看
  8. 「一夜白头」有科学依据了,减压可返黑 | 哥伦比亚大学最新研究
  9. 一身漏洞狂奔24年!人人都用的WiFi被曝重大漏洞,随时成为监控你的工具
  10. 平板就是生产力?东京大学研究者“辟谣”了,用纸笔记录,更有利于记忆