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
?
?
?
?
?
?
?