1.反射:
定义: 反射就是程序能够在运行时检查变量和值,求出它们的类型。
可以在运行时动态获取变量的相关信息
Import ("reflect")
为什么需要反射?
想象下:如果程序中每个变量都是我们自己定义的,那么在编译时就可以知道变量类型了,但是实际中并非如此,就需要我们在运行时检查变量,求出它的类型。这就需要用到反射。
在 Go 语言中,reflect 实现了运行时反射。reflect 包会帮助识别 interface{} 变量的底层具体类型和具体值。
几个函数:
a. reflect.TypeOf(val),获取变量的类型,返回reflect.Type类型
b. reflect.ValueOf(val),获取变量的值,返回reflect.Value类型
c. reflect.Value.Kind(),获取变量的类别,返回一个常量
d. reflect.Value.Interface(),转换成interface{}类型
变量接口及获取变量值之间的转换:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Name string 10 Age int 11 Score float32 12 } 13 14 func test(b interface{}) { 15 t := reflect.TypeOf(b) 16 fmt.Println("t: ", t) //t: main.Student 17 18 v := reflect.ValueOf(b) 19 fmt.Println("v: ", v) //v: {stu01 18 92} 20 21 k := v.Kind() 22 fmt.Println("k: ", k) //k: struct 23 24 iv := v.Interface() 25 fmt.Println("iv: ", iv) //iv: {stu01 18 92} 26 stu, ok := iv.(Student) 27 if ok { 28 fmt.Printf("%v %T ", stu, stu) //{stu01 18 92} main.Student 29 } 30 } 31 32 func main() { 33 var a Student = Student{ 34 Name: "stu01", 35 Age: 18, 36 Score: 92, 37 } 38 test(a) 39 }
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 10 var x float64 = 3.4 11 fmt.Println("type:", reflect.TypeOf(x)) //type: float64 12 v := reflect.ValueOf(x) 13 fmt.Printf("value:%v, type:%T ", v, v) //value:3.4, type: reflect.Valuetype 14 fmt.Println("type:", v.Type()) //type: float64 15 fmt.Println("kind:", v.Kind()) //kind: float64 16 fmt.Println("value:", v.Float()) //value: 3.4 17 18 fmt.Println(v.Interface()) //3.4 19 fmt.Printf("value is %5.2e ", v.Interface()) //value is 3.40e+00 20 y := v.Interface().(float64) //v -> Interface -> float64 -> y 21 fmt.Println(y) //3.4 22 }
2. reflect.Value.Kind()方法返回的常量
1 const ( 2 Invalid Kind = iota 3 Bool 4 Int 5 Int8 6 Int16 7 Int32 8 Int64 9 Uint 10 Uint8 11 Uint16 12 Uint32 13 Uint64 14 Uintptr 15 Float32 16 Float64 17 Complex64 18 Complex128 19 Array 20 Chan 21 Func 22 Interface 23 Map 24 Ptr 25 Slice 26 String 27 Struct 28 UnsafePointer 29 )
3. 获取变量的值:
reflect.ValueOf(x).Float()
reflect.ValueOf(x).Int()
reflect.ValueOf(x).String()
reflect.ValueOf(x).Bool()
4. 通过反射的来改变变量的值
reflect.Value.SetXX相关方法,比如:
reflect.Value.SetFloat(),设置浮点数
reflect.Value.SetInt(),设置整数
reflect.Value.SetString(),设置字符串
练习:(panic: reflect: reflect.Value.SetFloat using unaddressable value)
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 var a float64 10 fv := reflect.ValueOf(a) 11 fv.SetFloat(3.3) 12 fmt.Printf("%v ", a) 13 }
崩溃的原因:还是值类型和引用类型的原因。v := reflect.ValueOf(x) ,v是x的一个拷贝,修改v,x不会修改!
解决方法:传地址!
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 var a float64 = 1.0 10 11 fv := reflect.ValueOf(&a) 12 fv.Elem().SetFloat(3.3) //相当于var *fv float64; *fv = 3.3 13 fmt.Printf("%v ", a) //3.3 14 }
其中fv.Elem().Setxx用来获取指针指向的变量,相当于:
var a *int;
*a = 100
5. 用反射操作结构体
a. reflect.Value.NumField() 获取结构体中字段的个数
b. reflect.Value.Method(n).Call 来调用结构体中的方法
1 func (Value) Call 2 func (v Value) Call(in []Value) []Value 3 Call calls the function v with the input arguments in. For example, if len(in) == 3, v.Call(in) represents the Go call v(in[0], in[1], in[2]). Call panics if v's Kind is not Func. It returns the output results as Values. As in Go, each input argument must be assignable to the type of the function's corresponding input parameter. If v is a variadic function, Call creates the variadic slice parameter itself, copying in the corresponding values. 4 5 func (Value) Elem 6 func (v Value) Elem() Value 7 Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil. 8 9 func (Value) NumField 10 func (v Value) NumField() int 11 NumField returns the number of fields in the struct v. It panics if v's Kind is not Struct.
1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 ) 8 9 type Student struct { 10 Name string `json:"student_name"` 11 Age int 12 Score float32 13 Sex string 14 } 15 16 func (s Student) Print() { 17 fmt.Println("---start----") 18 fmt.Println(s) 19 fmt.Println("---end----") 20 } 21 22 func (s Student) Set(name string, age int, score float32, sex string) { 23 s.Name = name 24 s.Age = age 25 s.Score = score 26 s.Sex = sex 27 fmt.Println("set -----------") 28 } 29 30 func TestStruct(a interface{}) { 31 tye := reflect.TypeOf(a) 32 val := reflect.ValueOf(a) 33 kd := val.Kind() 34 // fmt.Println(tye, val, kd, val.Elem().Kind()) //*main.Student &{stu01 18 92.8 } ptr struct 35 36 if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct { 37 fmt.Println("expect struct") 38 return 39 } 40 41 num := val.Elem().NumField() //获取val 42 val.Elem().Field(0).SetString("stu1000") 43 for i := 0; i < num; i++ { 44 fmt.Printf("%d %v ", i, val.Elem().Field(i).Kind()) 45 } 46 47 fmt.Printf("struct has %d fields ", num) 48 49 tag := tye.Elem().Field(0).Tag.Get("json") 50 fmt.Printf("tag=%s ", tag) //tag=student_name 51 52 numOfMethod := val.Elem().NumMethod() //2 53 fmt.Printf("struct has %d methods ", numOfMethod) 54 var params []reflect.Value 55 val.Elem().Method(0).Call(params) 56 } 57 58 func main() { 59 var a Student = Student{ 60 Name: "stu01", 61 Age: 18, 62 Score: 92.8, 63 } 64 65 result, _ := json.Marshal(a) 66 fmt.Println("json result:", string(result)) 67 68 TestStruct(&a) 69 fmt.Println(a) 70 }
6. 反射中调用函数
Go中的函数是可以像普通的 int、float 等类型变量那样作为值的。例如:
1 package main 2 3 import "fmt" 4 5 func Print() { 6 fmt.Println("Hello world!") 7 } 8 9 func main() { 10 f := Print 11 f() 12 }
和函数作为变量类似,在反射中函数和方法的类型(Type)都是 reflect.Func,如果要调用函数的话,可以通过 Value的Call()方法。
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func Print() { 9 fmt.Println("Hello world!") 10 } 11 12 func main() { 13 f := Print 14 fv := reflect.ValueOf(f) 15 fmt.Println("fv is reflect.Func? ", fv.Kind() == reflect.Func) //fv is reflect.Func? true 16 fv.Call(nil) //Hello world! 17 }
Call函数的定义:func (v Value) Call(in []Value) []Value
Value 的 Call() 方法的参数是一个 Value 的 slice,对应的反射函数类型的参数,返回值也是一个 Value 的 slice,同样对应反射函数类型的返回值。
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func Print(str string) string { 9 str = fmt.Sprintf("hello %s", str) 10 return str 11 } 12 13 func main() { 14 fv := reflect.ValueOf(Print) 15 params := make([]reflect.Value, 1) // 传给Print的参数 16 params[0] = reflect.ValueOf("zhangsan") // 参数设置为"zhangsan" 17 rs := fv.Call(params) // rs作为结果接受函数的返回值 18 //result: hello zhangsan 19 fmt.Println("result:", rs[0].Interface().(string)) // rs[0].Interface() ok 20 }
7. 反射中调用方法
函数和方法可以说其实本质上是相同的,只不过方法与一个“对象”进行了“绑定”,方法是“对象”的一种行为,这种行为是对于这个“对象”的一系列操作,例如修改“对象”的某个属性。
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Name string 10 Age int 11 } 12 13 func (s *Student) SetName(name string) { 14 s.Name = name 15 } 16 17 func (s *Student) SetAge(age int) { 18 s.Age = age 19 } 20 21 func (s *Student) Print() string { 22 str := fmt.Sprintf("%s is %d years old.", s.Name, s.Age) 23 return str 24 } 25 26 func main() { 27 stu := &Student { 28 Name:"zhangsan", 29 Age:20, 30 } 31 32 ref := reflect.ValueOf(stu) 33 fmt.Println("Before:", ref.MethodByName("Print").Call(nil)[0]) //Before: zhangsan is 20 years old. 34 35 36 params := make([]reflect.Value, 1) 37 38 params[0] = reflect.ValueOf("lisi") 39 ref.MethodByName("SetName").Call(params) 40 fmt.Println(stu) //&{lisi 20} 41 42 params[0] = reflect.ValueOf(22) 43 ref.MethodByName("SetAge").Call(params) 44 45 fmt.Println(stu) //&{lisi 22} 46 fmt.Println("After:", ref.MethodByName("Print").Call(nil)[0]) //After: lisi is 22 years old. 47 }
练习1:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type NotknownType struct { 9 s1 string 10 s2 string 11 s3 string 12 } 13 14 func (n NotknownType) String() string { 15 return n.s1 + "-" + n.s2 + "-" + n.s3 16 } 17 18 var secret interface{} = NotknownType{"Ada", "Go", "Oberon"} 19 20 func main() { 21 value := reflect.ValueOf(secret) 22 typ := reflect.TypeOf(secret) 23 fmt.Println(typ) // Main.NotknownType 24 25 knd := value.Kind() 26 fmt.Println(knd) // struct 27 28 for i := 0; i < value.NumField(); i++ { 29 fmt.Printf("Field %d: %v ", i, value.Field(i)) 30 //value.Field(i).SetString("C#") 31 } 32 33 results := value.Method(0).Call(nil) 34 fmt.Println(results) // [Ada - Go - Oberon] 35 }
练习2:通过反射操作结构体
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type NotknownType struct { 9 s1 string 10 s2 string 11 s3 string 12 } 13 14 func (n NotknownType) String() string { 15 return n.s1 + "-" + n.s2 + "-" + n.s3 16 } 17 18 var secret interface{} = NotknownType{"Ada", "Go", "Oberon"} 19 20 func main() { 21 value := reflect.ValueOf(secret) 22 typ := reflect.TypeOf(secret) 23 fmt.Println(typ) // Main.NotknownType 24 25 knd := value.Kind() 26 fmt.Println(knd) // struct 27 28 for i := 0; i < value.NumField(); i++ { 29 fmt.Printf("Field %d: %v ", i, value.Field(i)) 30 //value.Field(i).SetString("C#") 31 } 32 33 results := value.Method(0).Call(nil) 34 fmt.Println(results) // [Ada - Go - Oberon] 35 }
练习3:通过反射修改结构体
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type T struct { 9 A int 10 B string 11 } 12 13 func main() { 14 t := T{23, "skidoo"} 15 s := reflect.ValueOf(&t).Elem() 16 typeOfT := s.Type() //main.T 17 for i := 0; i < s.NumField(); i++ { 18 f := s.Field(i) 19 fmt.Printf("%d: %s %s = %v ", i, 20 typeOfT.Field(i).Name, f.Type(), f.Interface()) 21 } 22 s.Field(0).SetInt(77) 23 s.Field(1).SetString("Sunset Strip") 24 fmt.Println("t is now", t) //t is now {77 Sunset Strip} 25 }
建议:反射是 Go 语言中非常强大和高级的概念,我们应该小心谨慎地使用它。使用反射编写清晰和可维护的代码是十分困难的。你应该尽可能避免使用它,只在必须用到它时,才使用反射。
图书管理系统v2版本开发:
实现一个图书管理系统v2,具有以下功能:
a. 增加用户登录、注册功能
b. 增加借书过期的图书界面
c. 增加显示热门图书的功能,被借次数最多的top10
d. 增加查看某个人的借书记录的功能
参考文献:
- https://studygolang.com/articles/13178 (Go 系列教程 - 反射)
- https://golang.org/pkg/reflect/
- https://www.cnblogs.com/52php/p/6337420.html