Golang面向API编程-interface(接口)
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
Golang并不是一种典型的面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)语言。它在语法上不支持类和继承的概念。没有继承是否就无法拥有多态行为了呢?答案是否定的,我们知道 Golang 中没有 class 的概念,而是通过 interface 类型转换支持在动态类型语言中常见的“鸭子类型”达到运行时多态的效果。
一.什么是interface
简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。换句话说,一个 interface 类型定义了一个“方法集合”作为其接口。 interface类型的变量可以保存含有属于这个interface类型的任何类型的值,这时我们就说这个类型实现了这个接口。未被初始化的interface类型变量的零值为空(nil)。
二.interface类型和值
接口类型实际上是一组method(方法)签名的清单。interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。接口也是一种数据类型。如果你声明了一个接口变量,这个变量能够存储任何实现该接口的对象类型。最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含 0 个method的interface。所以我喜欢给它起一个绰号叫“大胃王”。
定义一个interface以及调用方式如下:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 "strings" 13 ) 14 15 type Human struct { 16 Name string //定义姓名 17 string //内置的匿名字段,我们用它来定义家庭住址。 18 phone int //电话号码 19 } 20 21 type Student struct { 22 Human //匿名字段,其类型就是我们上面自定义的类型。 23 Classroom string //教室名称 24 Score float64 //考试成绩 25 } 26 27 type Teacher struct { 28 Human //匿名字段 29 Position string //职位信息 30 salary float64 //薪资情况 31 } 32 33 34 func (h Human) SayHi() { //定义结构体“Human”自我介绍的方法。 35 fmt.Printf("Hello, my name is 【%s】. My phone number is【%d】.My home address is【%s】\n", h.Name, 36 h.phone,h.string) //格式化输出是可以换行的哟。 37 } 38 39 40 func (h Human) Sing(Name string) { //定义结构体“Human”唱歌的方法 41 fmt.Printf("【%s】:我有一只小毛驴我从来也不骑,有一天我心血来潮骑它去赶集.....\n", Name) 42 } 43 44 45 func (t Teacher) SayHi() { //定义结构体“Teacher”自我介绍的方法。 46 fmt.Printf("Anyway, I'm your 【%s】 teacher, you can call me 【%s】,My salary is...【%f】\n", t.Position, 47 t.Human.Name,t.salary) 48 } 49 50 51 type Superman interface { //定义一个接口 52 SayHi() //这个接口包含“SayHi() ”方法。 53 Sing(Name string) //该接口也包含“Sing(Name string)”方法。 54 } 55 56 func main() { 57 yzj := Student{Human{"尹正杰", "北京", 7474741}, "三年级一班", 95} //初始化我们定义的结构体。我们也可以将这个过程叫做实例化。而将“yzj”叫做实例。 58 hsy := Student{Human{"韩森雨", "北京", 2424241}, "一年级五班", 100} 59 bingan := Teacher{Human{"饼干", "北京", 6464641}, "Golang", 30000} 60 61 62 var yinzhengjie Superman //声明一个变量,其类型为我们定义的接口。 63 64 yinzhengjie = yzj //注意了,这是接受了我们定义第一个类型。 65 yinzhengjie.SayHi() //并且可以调用这个类型下的“SayHi()”方法已经“Sing("高音唱")”方法。 66 yinzhengjie.Sing("高音唱") 67 68 69 fmt.Println("\n",strings.Repeat("*",30),"我是分割线",strings.Repeat("*",30),"\n") 70 71 yin := make([]Superman,3) //处理上面的那种最普遍的玩法,当然也可以用make方法将其定义成切片的方式。 72 yin[0], yin[1], yin[2] = hsy, bingan, yzj //用下标来区分不同的实力。这个时候我们可以发现Superman类型可以接受“Student”和“Teacher”类型的数据,尽管这是两个不同的结构体,但是照样可以通过一个接口来调用,因此我叫它“大胃王”。 73 74 for _, value := range yin{ //然后我们就可以同时调用3个方法啦! 75 value.SayHi() 76 } 77 } 78 79 80 81 #以上代码执行结果如下: 82 Hello, my name is 【尹正杰】. My phone number is【7474741】.My home address is【北京】 83 【高音唱】:我有一只小毛驴我从来也不骑,有一天我心血来潮骑它去赶集..... 84 85 ****************************** 我是分割线 ****************************** 86 87 Hello, my name is 【韩森雨】. My phone number is【2424241】.My home address is【北京】 88 Anyway, I'm your 【Golang】 teacher, you can call me 【饼干】,My salary is...【30000.000000】 89 Hello, my name is 【尹正杰】. My phone number is【7474741】.My home address is【北京】
通过上面的代码我们可以知道,interface 可以被任意的对象实现。同理,一个对象可以实现任意多个interface。你会发现interface 就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, go 通过 interface 实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
三.空interface
空interface(interface{})不包含任何的 method,正因为如此,所有的类型都实现了空interface。空 interface 对于描述起不到任何的作用(因为它不包含任何的 method),但是空interface 在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是瞬间就觉得interface很神奇!
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 "reflect" 13 ) 14 15 func Myecho(a interface{}) { 16 fmt.Printf("变量的值是:\033[31;1m【%v】\033[0m,其类型是:\033[31;1m【%v】\033[0m\n",a,reflect.TypeOf(a)) 17 } 18 19 func main() { 20 Name := "尹正杰" //我这里定义了三种不同的类型,即字符串,整数,字节数等等。 21 Age := 18 22 Language := []byte("Golang") 23 fmt.Println(reflect.TypeOf(Name),reflect.TypeOf(Age),reflect.TypeOf(Language)) 24 var yinzhengjie interface{} //定义一个空的interface,由于每种数据类型都实现了空interface。因此我们利用这个特性可以接受任意类型的数据。 25 yinzhengjie = Name 26 Myecho(yinzhengjie) 27 yinzhengjie = Age 28 Myecho(yinzhengjie) 29 yinzhengjie = Language 30 Myecho(yinzhengjie) 31 } 32 33 34 35 #以上代码输出结果如下: 36 string int []uint8 37 变量的值是:【尹正杰】,其类型是:【string】 38 变量的值是:【18】,其类型是:【int】 39 变量的值是:【[71 111 108 97 110 103]】,其类型是:【[]uint8】
四.interface 函数参数
interface 的变量可以持有任意实现该 interface 类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义 interface 参数,让函数接受各种类型的参数。举个例子:fmt.Println 是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
接下来我们就来模拟实现“fmt.Println()”的stringer方法吧:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "strconv" 12 "fmt" 13 ) 14 15 type Student struct { 16 Name string //定义姓名 17 age int //定义年龄 18 string //定义住址,这是匿名字段 19 } 20 21 func (s Student)String()string { //给Student实现来String方法,如果我们把String前面加个其他字母或是进行其他修改,可能会导致该方法的内容不会被调用。 22 return "My name is "+ s.Name+",I am "+strconv.Itoa(s.age)+" years old.I live in "+s.string 23 } 24 25 func main() { 26 yzj := Student{"尹正杰",18,"北京"} 27 fmt.Println("This people is :",yzj) 28 } 29 30 31 32 #以上代码输出结果如下: 33 This people is : My name is 尹正杰,I am 18 years old.I live in 北京
五.interface 变量存储的类型
我们知道interface 的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:“Comma-ok 断言” 和“switch 测试”。
1.Comma-ok 断言
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 import ( 10 "fmt" 11 "strconv" 12 ) 13 type Element interface{} 14 15 const ( //这是定义一个常量的关键字 16 pi = 3.14 17 ) 18 19 type Student struct { 20 Name string 21 age int 22 } 23 24 //定义了 String 方法,实现了fmt.Stringer 25 func (p Student) String() string { 26 return "(name: " + p.Name + " - age: "+strconv.Itoa(p.age)+ " years old!)" 27 } 28 func main() { 29 list := make([]Element, 4) 30 list[0] = 1 // 定义一个“int”类型的数据。 31 list[1] = "Hello" // 定义一个“string”类型的数据。 32 list[2] = Student{"Yinzhengjie", 18} // 定义一个“Student”类型的数据。 33 list[3] = pi //定义一个常量。 34 35 for index, element := range list { //接下来就是判断里面的每一个元素属于哪一种类型。 36 if value, ok := element.(int); ok { //判断当前的数据类型是否为“int”类型 37 fmt.Printf("list[%d] is an int and its value is %d\n", index, value) 38 } else if value, ok := element.(string); ok { //判断当前的数据类型是否为“string”类型 39 fmt.Printf("list[%d] is a string and its value is %s\n", index, value) 40 } else if value, ok := element.(Student); ok { //判断当前的数据类型是否为自定义的“Student”类型 41 fmt.Printf("list[%d] is a Student and its value is %s\n", index, value) 42 } else { 43 fmt.Printf("list[%d] is of a different type!", index) 44 } 45 } 46 } 47 48 49 50 51 #以上代码输出结果如下: 52 list[0] is an int and its value is 1 53 list[1] is a string and its value is Hello 54 list[2] is a Student and its value is (name: Yinzhengjie - age: 18 years old!) 55 list[3] is of a different type!
2.switch 测试
如果上面的那种方式你能看懂,并且之前我也分享过golang流程控制的笔记,那么下面的这种断言方式对你来说就是小case啦~从代码的易读性的话我推荐使用这种方式进行对数据类型的断言。
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 9 package main 10 11 import ( 12 "strconv" 13 "fmt" 14 ) 15 16 type Element interface {} 17 18 19 type Student struct { 20 Name string 21 age int 22 } 23 24 func (p Student) String() string { 25 return "My name is "+ p.Name+" and I am "+ strconv.Itoa(p.age) +" years old!" 26 } 27 28 func main() { 29 list := make([]Element,3) 30 list[0] = 1 31 list[1] = "yinzhengjie" 32 list[2] = Student{"yinzhengjie",18} 33 34 for k,v := range list { 35 switch value := v.(type) { //element.(type) 语法不能在switch 外的任何逻辑里面使用,如果你要在switch 外面判断一个类型就使用 comma-ok 。 36 case int: 37 fmt.Printf("list[%d] is an int and its value is %d\n",k,value) 38 case string: 39 fmt.Printf("list[%d] is an string and its value is %s\n",k,value) 40 case Student: 41 fmt.Printf("list[%d] is an Student and its value is %v\n",k,value) 42 default: 43 fmt.Printf("list[%d] is of a different\n",) 44 } 45 } 46 } 47 48 49 50 51 #以上代码输出结果如下: 52 list[0] is an int and its value is 1 53 list[1] is an string and its value is yinzhengjie 54 list[2] is an Student and its value is My name is yinzhengjie and I am 18 years old!
六.嵌入 interface
看到官方使用的嵌入interface方法你是否想到我们之前说的匿名字段啦?Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Struct 时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到 interface 里面,那不是更加完美了。如果一个interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了interface1 里面的method。接下来我们就来举个例子自定义实现以下嵌入interface的案例吧,具体代码如下:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 type Student struct { // 定义结构 Employee 13 Name string 14 age int 15 salary int 16 gender string 17 } 18 19 // 定义结构 Employee 的方法 20 func (self *Student) GetName() string { 21 return self.Name 22 } 23 24 func (self *Student) GetAge() int { 25 return self.age 26 } 27 28 func (self *Student) GetSalary() int { 29 return self.salary 30 } 31 32 func (self *Student) Help() { 33 fmt.Println("Don't ask me, ask me, I won't tell you!") 34 } 35 36 func (self *Student) GetGender() string { 37 return self.gender 38 } 39 40 type MiyoshiStudents interface { // 定义接口类型 MiyoshiStudents 包含获取基本信息的方法 41 GetName() string 42 GetAge() int 43 } 44 45 type Teacher interface { // 定义接口类型 Teacher 包含获取薪水的方法且 Teacher 接口中嵌入了 MiyoshiStudents 接口,前者将获取后者的所有方法。 46 MiyoshiStudents //这就是嵌入interface和嵌入匿名字段的用法有点相似。 47 GetSalary() int 48 Help() 49 } 50 51 func main() { 52 yzj := Student{ // yzj 实现了 MiyoshiStudents 和 Teacher 这两个接口 53 Name: "尹正杰", 54 age: 18, 55 salary: 100000000, 56 gender: "Male", 57 } 58 fmt.Println("yzj is: ", yzj) 59 yzj.Help() 60 fmt.Println("yzj.name = ", yzj.GetName()) 61 fmt.Println("yzj.age = ", yzj.GetAge()) 62 fmt.Println("yzj.salary = ", yzj.GetSalary()) 63 64 var yinzhengjie Teacher = &yzj 65 66 switch yinzhengjie.(type) { // 接口类型转换,从超集到子集的转换是可以的,从方法集的子集到超集的转换会导致编译错误,这种情况下 switch 不支持 fallthrough。 67 case nil: 68 fmt.Println("空接口(nil)") 69 case MiyoshiStudents: 70 fmt.Println("MiyoshiStudents 接口") 71 default: 72 fmt.Println("位置接口") 73 } 74 } 75 76 77 78 79 #以上代码执行结果如下: 80 yzj is: {尹正杰 18 100000000 Male} 81 Don't ask me, ask me, I won't tell you! 82 yzj.name = 尹正杰 83 yzj.age = 18 84 yzj.salary = 100000000 85 MiyoshiStudents 接口
七.匿名接口
还记得匿名字段吗?我们可以在一个结构体中定义一个匿名字段,这个匿名字段可以是内置的也可以是我们自定义的,而interface是一种特殊的数据类型,因为golang认为所有的数据类型都实现了空接口,也就是说所有数据都是空interface的子集。换句话说,我们可以说一个空的interface是可以接受任何类型的数据的。通过这一点,它会给我们很多启发吗,我们可以通过interface来接受任何类型的参数,也可以通过interface来返回任何类型的参数,接下来我们一起看下匿名interface的使用案例吧:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 13 type Student struct { // 定义结构体Student 14 Name string 15 age int 16 salary int 17 gender string 18 } 19 20 func (self *Student) GetName() string { // 定义结构 Student 的方法 21 return self.Name 22 } 23 24 func (self *Student) GetAge() int { 25 return self.age 26 } 27 28 func (self *Student) GetSalary() int { 29 return self.salary 30 } 31 32 func (self *Student) Help() { 33 fmt.Println("This is help info.") 34 } 35 36 type MiyoshiStudents struct { 37 GetInfo interface { // 匿名接口可以被用作变量或者结构属性类型,我们定义了一个“GetInfo”的匿名接口,里面可以存储各种数据属性。 38 GetGender() string 39 GetSalary() int 40 GetAge() int 41 GetName() string 42 } 43 } 44 45 func (self *Student) GetGender() string { 46 return self.gender 47 } 48 49 50 func main() { 51 yzj := MiyoshiStudents{&Student{ // 匿名接口对象的使用 52 Name: "尹正杰", 53 age: 18, 54 salary: 10000000000, 55 gender: "男孩", 56 }} 57 fmt.Println("姓名:",yzj.GetInfo.GetName()) 58 fmt.Println("年龄:",yzj.GetInfo.GetAge()) 59 fmt.Println("性别: ", yzj.GetInfo.GetGender()) 60 fmt.Println("期望薪资:",yzj.GetInfo.GetSalary()) 61 62 } 63 64 65 66 #以上代码运行结果如下: 67 姓名: 尹正杰 68 年龄: 18 69 性别: 男孩 70 期望薪资: 10000000000
八.进阶知识-Go语言的反射三定律
1.什么是反射
反射是指程序可以访问、检测和修改它本身状态或行为的一种能力,所以给的定义就是说明了它能干嘛。我们平时用反射主要做:获取类型的相关信息,动态调用方法,动态构造对象,从程序集中获得类型。
2.为什么需要反射
Go是静态类型语言。每个变量都有且只有一个静态类型,在编译时就已经确定。尽管变量两个边路都具有共同的底层数据类型,但它们的只要他们静态类型不一样。不经过类型转换直接相互赋值时,编译器会报错。相信大家通过一段熟悉的代码就应该明白了:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 type Myint int 13 14 type Element interface{} //定义一个空接口 15 16 var ( 17 x int 18 y Myint //尽管变量 x 和 y 具有共同的底层类型 int,但它们的静态类型并不一样。 19 ) 20 func main() { 21 x = 100 22 y = 100 23 list := make([]Element, 2) 24 list[0] = x 25 list[1] = y 26 fmt.Println(list) 27 for k,v := range list{ 28 switch value := v.(type) { //我们队数据类型进行断言。 29 case int: 30 fmt.Printf("list[%d] is an int(整型) and its value is %d\n",k,value) 31 case string: 32 fmt.Printf("list[%d] is an string(字符串) and its value is %d\n",k,value) 33 case Myint: 34 fmt.Printf("list[%d] is an Myint(自定义类型) and its value is %d\n",k,value) 35 default: 36 fmt.Printf("list[%d] is of a different\n",) 37 } 38 } 39 } 40 41 42 43 44 #以上代码执行结果如下: 45 [100 100] 46 list[0] is an int(整型) and its value is 100 47 list[1] is an Myint(自定义类型) and its value is 100
3.反射第一定律:从接口值到反射对象的反射(Reflection goes from interface value to reflection object)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 "reflect" 13 ) 14 15 func main() { 16 var yzj float64 = 5.2 17 fmt.Println("type:", reflect.TypeOf(yzj)) //reflect.Typeof 签名里就包含了一个空接口。当我们调用reflect.Typeof(yzj)的时候, 18 // yzj首先被保存到一个空接口中,这个空接口然后被作为参数传递。reflect.Typeof 会把这个空接口拆包(unpack)恢复出类型信息。 19 20 fmt.Println("value:", reflect.ValueOf(yzj)) //当然,reflect.Valueof可以把值恢复出来,Valueof方法会返回一个Value类型的对象 21 } 22 23 24 25 #以上代码执行结果如下: 26 type: float64 27 value: 5.2
reflect.Type和reflect.Value这两种类型都提供了大量的方法让我们可以检查和操作这两种类型。有以下两点要注意:
第一,Value类型有一个Type方法可以返回reflect.Value类型的Type,这个方法返回的是值的静态类型即“static type”,也就是说如果定义了“type MyType string”,那么这个函数返回的是“MyType”类型而不是“string”。有关Value类型的带有名字诸如“String”,“Int”,“Uint”“Bytes”等等的方法可让我们获取存在里面的值。
第二,Type和Value都有一个Kind方法可以返回一个常量用于指示一个项到底是以什么形式存储的,也就是底层类型即“underlying type”。这些常量包括:Unit, Float64, Slice等等。
具体用法我们可以以下的代码:
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "reflect" 12 "fmt" 13 ) 14 15 type MyType string 16 17 func main() { 18 var y MyType = "yinzhengjie" 19 Type := reflect.TypeOf(y) //得到类型的元数据,通过 t 我们能获取类型定义里面的所有元素. 20 Value := reflect.ValueOf(y) //得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值. 21 fmt.Println("type\t\t\t:",Type) 22 fmt.Println("underlying type :",Type.Kind()) //Type和Value都有一个Kind方法可以返回一个常量,以判断出它的底层数据到底是什么类型。 23 fmt.Println("value\t\t\t:",Value) 24 fmt.Println("static type :",Value.Type()) //Value类型有一个Type方法可以返回reflect.Value类型的Type(这个方法返回的是值的静态类型即static type.) 25 fmt.Println("underlying type :",Value.Kind()) 26 fmt.Println("kind is string :",Value.Kind() == reflect.String) 27 fmt.Println("value\t\t\t:",Value.String()) //通过Value类型String方法来让我们获取存在里面的值。如果是底层数据是“int”就用“Int”方法获取。 28 } 29 30 31 32 33 #以上代码执行结果如下: 34 type : main.MyType 35 underlying type : string 36 value : yinzhengjie 37 static type : main.MyType 38 underlying type : string 39 kind is string : true 40 value : yinzhengjie
4.反射第二定律:从反射对象到接口值的反射(Reflection goes from reflection object to interface value)
就像物理学上的作用力和反作用力,我们可以从接口值到反射对象,与此同时,我们也可以从反射对象到接口值。
给定一个reflect.Value,我们能用Interface方法把它恢复成一个接口值;效果上就是这个Interface方法把类型和值的信息打包成一个接口表示并且返回结果。简要的说,Interface方法是Valueof函数的逆,除了它的返回值的类型总是interface{}静态类型。重申一遍:反射就是从接口值到反射对象,然后再反射回来。(Reflection goes from interface value to reflection object and back again.)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "reflect" 12 "fmt" 13 ) 14 15 type MyType string 16 17 18 func main() { 19 var y MyType = "yinzhengjie" 20 Value := reflect.ValueOf(y) //得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值. 21 fmt.Println(Value) //Value是一个reflect.Value. 22 23 x := Value.Interface() //我们想要的是Value里面保存的具体值.我们不需要对v.Interface方法的结果调用类型断言 24 fmt.Println(x) 25 } 26 27 28 29 #以上代码执行结果如下: 30 yinzhengjie 31 yinzhengjie
5.反射第三定律:为了修改一个反射对象,值必须是settable的(To modify a reflection object, the value must be settable)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "reflect" 12 "fmt" 13 ) 14 15 func main() { 16 var yzj string = "yinzhengjie" 17 p := reflect.ValueOf(&yzj) //注意这里哦!我们把yzj地址传进去了! 18 fmt.Println("type of p:", p.Type()) //我们是讲地址传进去的,所以得到的应该是一个指针类型的string. 19 fmt.Println("settability of p:", p.CanSet()) //反射对象p不是settable的,因此返回值应该是一个false! 20 21 v := p.Elem() //反射对象p不是settable的,但是我们想要设置的不是p,而是(效果上来说)*p,为了得到p指向的东西,我们调用Value的Elem方法。 22 23 fmt.Println(v.Interface()) //查看v里面的值 24 s := v.String() 25 s = "尹正杰" //我们此处修改的只是“yzj”变量中的一个副本 26 fmt.Println(s) 27 fmt.Println(yzj) //忧郁s修改的是副本,所以对本尊是一点影响的都没有的,源数据应该还是“yinzhengjie” 28 29 30 fmt.Println("settability of v:", v.CanSet()) //反射对象v是settable的,因此返回值应该是一个true! 31 v.SetString("Golang") //想要修改源数据,还是得调用该SetString,SetInt,SetFloat,等方法去修改相应的数据类型。 32 fmt.Println(yzj) //由于已经通过SetString方法对源数据进行了修改,因此我们再看yzj这个变量的值就已经被修改了。 33 } 34 35 36 37 38 #以上代码执行结果如下: 39 type of p: *string 40 settability of p: false 41 yinzhengjie 42 尹正杰 43 yinzhengjie 44 settability of v: true 45 Golang