本文参考 http://c.biancheng.net/golang/reflect/

反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射

1. 反射概念

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

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

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

2. 获取类型信息

Go 语言中通过调用 reflect.TypeOf 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。 reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。

当然,我们也可以将一个接口值传递给一个 reflect.TypeOf 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。

实际上, reflect.TypeOf 函数的唯一参数的类型为 interface{}reflect.TypeOf 函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type 值。

使用 reflect.TypeOf() 函数可以获得任意值的类型对象( reflect.Type ),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:

package mainimport ("fmt""reflect"
)func main() {var a int// 通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()typeOfA := reflect.TypeOf(a)// 通过 typeOfA 类型对象的成员函数,可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 intfmt.Println(typeOfA.Name(), typeOfA.Kind())   // int int}
func main() {s := "hello"sv := reflect.ValueOf(s)st := reflect.TypeOf(s)fmt.Println(sv, st) // hello string
}

2.1 反射的类型(Type)与种类(Kind)

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

反射种类(Kind)的定义:

Go 程序中的类型( Type )指的是系统原生数据类型,如 intstringboolfloat32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

种类( Kind )指的是对象归属的品种,在 reflect 包中有如下定义:

type Kind uintconst (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        // 底层指针
)

2.2 从类型对象中获取类型名称和种类

Go 语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。

类型归属的种类( Kind )使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。

package mainimport ("fmt""reflect"
)// 定义一个Enum类型
type Enum intconst (Zero Enum = 0
)func main() {// 声明一个空结构体type cat struct {}// 将 cat 实例化,并且使用 reflect.TypeOf() 获取被实例化后的 cat 的反射类型对象typeOfCat := reflect.TypeOf(cat{})// 显示反射类型对象的名称和种类fmt.Println(typeOfCat.Name(), typeOfCat.Kind()) // cat struct// 获取Zero常量的反射类型对象typeOfA := reflect.TypeOf(Zero)// 显示反射类型对象的名称和种类fmt.Println(typeOfA.Name(), typeOfA.Kind()) // Enum int}

3. 获取指针指向的元素类型

Go 语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,代码如下:

package mainimport ("fmt""reflect"
)func main() {// 声明一个空结构体type cat struct {}// 创建cat的实例,ins 是一个 *cat 类型的指针变量ins := &cat{}// 获取结构体指针实例的反射类型对象typeOfCat := reflect.TypeOf(ins)// 输出指针变量的类型名称和种类// Go 语言的反射中对所有指针变量的种类都是 Ptr,但注意,指针变量的类型名称是空,不是 *catfmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())// 取指针类型的元素类型,也就是 cat 类型。这个操作不可逆,不可以通过一个非指针类型获取它的指针类型typeOfCat = typeOfCat.Elem()// 显示反射类型对象的名称和种类fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())}

输出:

name:'' kind:'ptr'
element name: 'cat', element kind: 'struct'

4. 获取结构体成员类型

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

reflect.Value 不同,reflect.Type 是一个接口,而不是一个结构体,所以也只能使用它的方法。

以下是 reflect.Type 接口常用的方法。从这个列表来看,大部分都和 reflect.Value 的方法功能相同。

type Type interface {Implements(u Type) boolAssignableTo(u Type) boolConvertibleTo(u Type) boolComparable() bool//以下这些方法和Value结构体的功能相同Kind() KindMethod(int) MethodMethodByName(string) (Method, bool)NumMethod() intElem() TypeField(i int) StructFieldFieldByIndex(index []int) StructFieldFieldByName(name string) (StructField, bool)FieldByNameFunc(match func(string) bool) (StructField, bool)NumField() int
}

其中几个特有的方法如下:

  • Implements 方法用于判断是否实现了接口 u

  • AssignableTo 方法用于判断是否可以赋值给类型 u ,其实就是是否可以使用 = ,即赋值运算符;

  • ConvertibleTo 方法用于判断是否可以转换成类型 u,其实就是是否可以进行类型转换;

  • Comparable 方法用于判断该类型是否是可比较的,其实就是是否可以使用关系运算符进行比较。

与成员获取相关的 reflect.Type 的方法如下表所示。结构体成员访问的方法列表:

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机
NumField() int 返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机
FieldByNameFunc( match func(string) bool) (StructField,bool) 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机

4.1 结构体字段类型

reflect.TypeField() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructFieldType 字段进一步获取结构体成员的类型信息。StructField 的结构如下:

type StructField struct {Name string          // 字段名PkgPath string       // 字段在结构体中的路径Type      Type       // 字段本身的反射类型对象,类型为 reflect.Type,Tag       StructTag  // 结构体标签,为结构体字段标签的额外信息,可以单独提取Offset    uintptr    // 字段在结构体中的相对偏移Index     []int      // Type.FieldByIndex中的返回的索引值Anonymous bool       // 是否为匿名字段
}

4.2 遍历结构体的字段和方法

可以通过 NumField 方法获取结构体字段的数量,然后使用 for 循环,通过 Field 方法就可以遍历结构体的字段,并打印出字段名称。同理,遍历结构体的方法也是同样的思路,代码也类似,如下所示:

package mainimport ("fmt""reflect"
)type person struct {Name stringAge  int
}func (p person) String() string {return fmt.Sprintf("Name is %s,Age is %d", p.Name, p.Age)
}func main() {p := person{Name: "wohu", Age: 18}pt := reflect.TypeOf(p)//遍历person的字段for i := 0; i < pt.NumField(); i++ {fmt.Println("字段:", pt.Field(i).Name)}//遍历person的方法for i := 0; i < pt.NumMethod(); i++ {fmt.Println("方法:", pt.Method(i).Name)}
}

4.3 获取成员反射信息

反射访问结构体成员类型及信息:

package mainimport ("fmt""reflect"
)func main() {// 声明一个空结构体type cat struct {Name string// 带有结构体tag的字段Type int `json:"type" id:"100"`// `json:"type" id:"100"` 这个字符串在 Go 语言中被称为 Tag(标签)。// 一般用于给字段添加自定义信息,方便其他模块根据信息进行不同功能的处理。}// 创建cat的实例ins := cat{Name: "mimi", Type: 1}// 获取结构体实例的反射类型对象typeOfCat := reflect.TypeOf(ins)// 遍历结构体所有成员for i := 0; i < typeOfCat.NumField(); i++ {// 获取每个成员的结构体字段类型fieldType := typeOfCat.Field(i)    // 使用 reflect.Type 的 Field() 方法返回的结构不再是 reflect.Type 而是StructField 结构体。// 输出成员名和tagfmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)}// 通过字段名, 找到字段类型信息if catType, ok := typeOfCat.FieldByName("Type"); ok {// 从tag中取出需要的tagfmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))}
}

输出结果:

name: Name  tag: ''
name: Type  tag: 'json:"type" id:"100"'
type 100

5. 获取结构体成员的值

reflect.Value 可以通过函数 reflect.ValueOf 获得,下面我将为你介绍它的结构和用法。

Go 语言中,reflect.Value 被定义为一个 struct 结构体,它的定义如下面的代码所示:

type Value struct {typ *rtypeptr unsafe.Pointerflag
}

我们发现 reflect.Value 结构体的字段都是私有的,也就是说,我们只能使用 reflect.Value 的方法。现在看看它有哪些常用方法,如下所示:

//针对具体类型的系列方法
//以下是用于获取对应的值
Bool
Bytes
Complex
Float
Int
String
Uint
CanSet //是否可以修改对应的值
以下是用于修改对应的值
Set
SetBool
SetBytes
SetComplex
SetFloat
SetInt
SetString
Elem //获取指针指向的值,一般用于修改对应的值
//以下Field系列方法用于获取struct类型中的字段
Field
FieldByIndex
FieldByName
FieldByNameFunc
Interface //获取对应的原始类型
IsNil //值是否为nil
IsZero //值是否是零值
Kind //获取对应的类型类别,比如Array、Slice、Map等
//获取对应的方法
Method
MethodByName
NumField //获取struct类型中字段的数量
NumMethod//类型上方法集的数量
Type//获取对应的reflect.Type

看着比较多,其实就三类:

  • 一类用于获取和修改对应的值;
  • 一类和 struct 类型的字段有关,用于获取对应的字段;
  • 一类和类型上的方法集有关,用于获取对应的方法;

5.1 获取原始类型

在上面的例子中,我通过 reflect.ValueOf 函数把任意类型的对象转为一个 reflect.Value,而如果想逆向转回来也可以,reflect.Value 为我们提供了 Inteface 方法,如下面的代码所示:

func main() {s := "hello"sv := reflect.ValueOf(s)ss := sv.Interface().(string)fmt.Println(sv, ss)   // hello hello
}

5.2 修改对应的值

已经定义的变量可以通过反射在运行时修改,比如上面的示例 s=“hello”,修改为 “world”, 如下所示:

func main() {s := "hello"sv := reflect.ValueOf(&s) // 必须是&s,否则报错// panic: reflect: call of reflect.Value.Elem on string Valuesv.Elem().SetString("world")fmt.Println(s) // world
}

这样就通过反射修改了一个变量。因为 reflect.ValueOf 函数返回的是一份值的拷贝,所以我们要传入变量的指针才可以。 因为传递的是一个指针,所以需要调用 Elem 方法找到这个指针指向的值,这样才能修改。 最后我们就可以使用 SetString 方法修改值了。

要修改一个变量的值,有几个关键点:传递指针(可寻址),通过 Elem 方法获取指向的值,才可以保证值可以被修改,reflect.Value 为我们提供了 CanSet 方法判断是否可以修改该变量。

那么如何修改 struct 结构体字段的值呢?参考变量的修改方式,可总结出以下步骤:

  1. 传递一个 struct 结构体的指针,获取对应的 reflect.Value

  2. 通过 Elem 方法获取指针指向的值;

  3. 通过 Field 方法获取要修改的字段;

  4. 通过 Set 系列方法修改成对应的值。

运行下面的代码,你会发现变量 p 中的 Name 字段已经被修改为张三了。

func main() {p := person{Name: "wohu", Age: 18}ppv := reflect.ValueOf(&p)ppv.Elem().Field(0).SetString("张三")fmt.Println(p) // {张三 18}
}type person struct {Name stringAge  int
}

最后再来总结一下通过反射修改一个值的规则。

  • 可被寻址,通俗地讲就是要向 reflect.ValueOf 函数传递一个指针作为参数。

  • 如果要修改 struct 结构体字段值的话,该字段需要是可导出的,而不是私有的,也就是该字段的首字母为大写。

  • 记得使用 Elem 方法获得指针指向的值,这样才能调用 Set 系列方法进行修改。

记住以上规则,你就可以在程序运行时通过反射修改一个变量或字段的值。

5.3 获取对应的底层类型

底层类型是什么意思呢?其实对应的主要是基础类型,比如接口、结构体、指针…因为我们可以通过 type 关键字声明很多新的类型。

比如在上面的例子中,变量 p 的实际类型是 person,但是 person 对应的底层类型是 struct 这个结构体类型,而 &p 对应的则是指针类型。我们来通过下面的代码进行验证:

type person struct {Name stringAge  int
}func main() {p := person{Name: "wohu", Age: 18}ppv := reflect.ValueOf(&p)fmt.Println(ppv.Kind())   // ptrpv := reflect.ValueOf(p)fmt.Println(pv.Kind())   // struct
}

Kind 方法返回一个 Kind 类型的值,它是一个常量,具体可见 2.1 节

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

方 法 备 注
Field(i int) Value 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机
NumField() int 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByIndex(index []int) Value 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) Value 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机

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

package mainimport ("fmt""reflect"
)// 定义结构体
type dummy struct {a intb string// 嵌入字段float32boolnext *dummy
}func main() {// 值包装结构体d := reflect.ValueOf(dummy{next: &dummy{},})// 获取字段数量fmt.Println("NumField", d.NumField())// 获取索引为2的字段(float32字段)floatField := d.Field(2)// 输出字段类型fmt.Println("Field", floatField.Type())// 根据名字查找字段fmt.Println("FieldByName(\"b\").Type", d.FieldByName("b").Type())// 根据索引查找值中, next字段的int字段的值fmt.Println("FieldByIndex([]int{4, 0}).Type()", d.FieldByIndex([]int{4, 0}).Type())
}

6. Go 语言结构体标签

通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(Struct Tag)。结构体标签是对结构体字段的额外信息标签。

6.1 结构体标签格式

Tag 在结构体字段后方书写的格式如下:

`key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

6.2 从结构体标签中获取值

StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:

  • func(tag StructTag)Get(key string)string
    根据 Tag 中的键获取对应的值,例如 key1:"value1"key2:"value2" 的 Tag 中,可以传入“key1”获得“value1”。
  • func(tag StructTag)Lookup(key string)(value string,ok bool)
    根据 Tag 中的键,查询值是否存在

6.3 结构体标签格式错误导致的问题

编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,参见下面这个例子:

package mainimport ("fmt""reflect"
)func main() {type cat struct {Name stringType int `json: "type" id:"100"`}typeOfCat := reflect.TypeOf(cat{})if catType, ok := typeOfCat.FieldByName("Type"); ok {fmt.Println(catType.Tag.Get("json"))}}

代码输出空字符串,并不会输出期望的 type。

第 12 行中,在json:"type"之间增加了一个空格。这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。

7. 反射获取值信息

当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。

一个 reflect.Value 值的 CanSet 方法将返回此 reflect.Value 值代表的 Go 值是否可以被修改(可以被赋值)。如果一个 Go 值可以被修改,则我们可以调用对应的 reflect.Value 值的 Set 方法来修改此 Go 值。

注意:reflect.ValueOf 函数直接返回的 reflect.Value 值都是不可修改的。

反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。Go 语言中使用 reflect.Value 获取和设置变量的值。

7.1 使用反射值对象包装任意值

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

value := reflect.ValueOf(rawValue)

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

7.2 从反射值对象获取被包装的值

可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。

方法名 说 明
Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回

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

package mainimport ("fmt""reflect"
)func main() {// 声明整型变量a并赋初值var a int = 1024// 获取变量a的反射值对象valueOfA := reflect.ValueOf(a)// 获取interface{}类型的值, 通过类型断言转换var getA int = valueOfA.Interface().(int)// 获取64位的值, 强制类型转换为int类型var getA2 int = int(valueOfA.Int())fmt.Println(getA, getA2)  // 1024 1024
}

8. 判断反射值的空和有效性

反射值对象( reflect.Value )提供一系列方法进行零值和空判定,如下表所示。

方 法 说 明
IsNil() bool 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。

下面的例子将会对各种方式的空指针进行 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())
}

9. 通过反射修改变量的值

10. 通过类型信息创建实例

11. 通过反射调用函数

12. 反射定律

反射是计算机语言中程序检视其自身结构的一种方法,它属于元编程的一种形式。反射灵活、强大,但也存在不安全。它可以绕过编译器的很多静态检查,如果过多使用便会造成混乱。为了帮助开发者更好地理解反射,Go 语言的作者在博客上总结了反射的三大定律。

  1. 任何接口值 interface{} 都可以反射出反射对象,也就是 reflect.Value 和 reflect.Type,通过函数 reflect.ValueOf 和 reflect.TypeOf 获得。

  2. 反射对象也可以还原为 interface{} 变量,也就是第 1 条定律的可逆性,通过 reflect.Value 结构体的 Interface 方法获得。

  3. 要修改反射的对象,该值必须可设置,也就是可寻址,参考上节课修改变量的值那一节的内容理解。

小提示:任何类型的变量都可以转换为空接口 intferface{},所以第 1 条定律中函数 reflect.ValueOf 和 reflect.TypeOf 的参数就是 interface{},表示可以把任何类型的变量转换为反射对象。在第 2 条定律中,reflect.Value 结构体的 Interface 方法返回的值也是 interface{},表示可以把反射对象还原为对应的类型变量。

一旦你理解了这三大定律,就可以更好地理解和使用 Go 语言反射。

总结
在反射中,reflect.Value 对应的是变量的值,如果你需要进行和变量的值有关的操作,应该优先使用 reflect.Value,比如获取变量的值、修改变量的值等。reflect.Type 对应的是变量的类型,如果你需要进行和变量的类型本身有关的操作,应该优先使用 reflect.Type,比如获取结构体内的字段、类型拥有的方法集等。

此外我要再次强调:反射虽然很强大,可以简化编程、减少重复代码,但是过度使用会让你的代码变得复杂混乱。所以除非非常必要,否则尽可能少地使用它们。

Go 学习笔记(39)— Go 反射相关推荐

  1. Mcad学习笔记之通过反射调用類的方法,屬性,字段,索引器(2種方法)

    相关文章导航 Sql Server2005 Transact-SQL 新兵器学习总结之-总结 Flex,Fms3相关文章索引 FlexAir开源版-全球免费多人视频聊天室,免费网络远程多人视频会议系统 ...

  2. IOS学习笔记39——拍照、从相册选图并对图片进行裁剪

    2013第一篇,大家新年快乐!感谢一直关注我博客的同学们,有你们的支持我才有动力越做越好!有阵子没写博客了,因为前阵子着实比较忙,没时间整理,今天主要实现一个小Demo,我们知道在Instagram或 ...

  3. Linux学习笔记39——任务调度:什么是例行性工作调度、仅执行一次的工作调度、循环执行的例行性工作调度、可唤醒停机期间的工作任务

    一.什么是例行性工作调度 1,Linux 工作调度的种类: at, cron 2,CentOS Linux 系统上常见的例行性工作 二,仅执行一次的工作调度 1,atd 的启动与 at 运行的方式 a ...

  4. 【Unity Shader学习笔记】实现反射与折射模拟水面、使用grabPass与环境贴图

    文章目录 写在前面 一个水波效果 大致组成部分与对应的实现方案 交界线与深度贴图 折射效果与GrabPass 使用Cubemap与法线信息来模拟反射 在正确的地点创建对应的cubemap 通过贴图获取 ...

  5. 学习笔记(39):Python实战编程-标签

    立即学习:https://edu.csdn.net/course/play/19711/343101?utm_source=blogtoedu 标签--文字标签和图片标签 1.文字标签 关键代码: l ...

  6. 安卓学习笔记39:浏览网页、网页与安卓通信

    文章目录 零.学习目标 一.Intent十二种常用功能 1.浏览网页 2.浏览地图 3.调出拨打电话界面 4.直接拨打电话 5.卸载应用程序 6.安装应用程序 7.播放存储卡音乐 8.调用发邮件 9. ...

  7. 【黑马程序员】 学习笔记 - Java新技术 - 反射

    ----------------------android培训.java培训.期待与您交流!---------------------- Java新技术- 反射 反射就是把Java类中的各种成分映射成 ...

  8. Go核心开发学习笔记(廿九) —— 反射

    反射使用的地方 序列化和反序列化时,如果希望序列时将结构体字段名称大写转换成小写,json:"xxx" 这里就用到了反射. 两个匿名函数变量,定义一个适配器函数用作统一处理接口: ...

  9. 大数据学习笔记39:Hive - 内置函数(2)

    文章目录 一.汇聚去重函数:collect_set (一)案例1:单列数据去重 1.创建数据文件nums.txt 2.将文件上传到HDFS的/cset目录 3.基于/cset目录创建hive外部表nu ...

  10. C++学习笔记39:进程概念

    进程的基本概念 进程是描述程序执行过程和资源共享的基本单位 主要目的:控制和协调程序的执行 进程相关函数 用户与组ID函数 创建进程:system(),fork(),exec() 终止进程:kill( ...

最新文章

  1. jenkins+sonarqube流水线脚本模板
  2. 64 安装_解决“不能安装 64 位Office,因已安装 32 位 Office 产品”问题
  3. WPF Interaction框架简介(一)——Behavior
  4. 十分钟教你开发EOS智能合约
  5. java.lang.System
  6. HDOJ 1247 HDU 1247 Hat’s Words ACM 1247 IN HDU
  7. 《学习之道》第九章不要突击工作
  8. Android 一直往文件写数据_( 十 ) 小众但好用:通过 Google drive 备份与同步 Keepass 数据库...
  9. java内联_JAVA中的内联函数
  10. android 正方形按钮,Android《FloatingActionButton》
  11. 微积分经典概念:极限、连续与函数
  12. 一道看似简单的sql需求却难倒各路高手 - 你也来挑战下吗?
  13. jsp论文参考文献(2020最新)
  14. UVC系列3-研究UVC控制协议
  15. hive sql列转行
  16. HTML背景图片的设置
  17. 软件产品案例分析(团队)
  18. 中秋之际,我想给月亮做一个智能化改造
  19. matlab 自动生成陷波滤波器算法实现
  20. CNN Matlab例子RGB_MATLAB如何提取曲线原始数据

热门文章

  1. 你听说过反摩尔定律吗?
  2. 2022-2028年中国钽酸锂单晶行业市场调查分析及投资发展潜力报告
  3. 用python快速画小猪佩奇
  4. redis 双写一致性 看一篇成高手系列1
  5. 彻底解决python打印结果省略号的问题显示宽度
  6. LeetCode中等题之煎饼排序
  7. LeetCode简单题之最常见的单词
  8. 工具箱支持汽车质量人工智能
  9. 客快物流大数据项目(十一):Docker应用部署
  10. Excution failed for task ':app:transformClassWithDexForDebug'