Go-知识反射
- 1. 接口
- 1.1 类型
- 1.2 interface 类型
- 1.2.1 interface 变量
- 1.2.2 实现接口
- 1.2.3 复合类型
- 1.2.4 空 interface
- 2. 反射定律
- 2.1 reflect 包
- 2.2 反射可以将 interface 类型变量转换为反射对象
- 2.3 反射可以将反射对象还原成 interface 对象
- 2.4 反射对象可修改,value 值必须是可设置的
- 3. 思考 接口实现和接口继承
gitio: https://a18792721831.github.io/
1. 接口
1.1 类型
Go 语言是静态类型语言,比如 int, float32,[]byte 等等
https://blog.csdn.net/a18792721831/article/details/135572152
每个变量都有一个静态类型,在编译时就确定了。
type Myint int
var i int
var j Myint
在上面的代码中, i 和 j 是不同的静态类型,尽管二者的底层类型都是 int, 但是在没有类型转换的情况下是不可以相互赋值的
除了基础类型,还有一些由基础类型组成的复合类型,比如数组,结构体,之神,切片,map和channel 等
同时 interface 也是一种复合类型。
1.2 interface 类型
interface 类型代表一个特定的方法集,方法集中的方法称为接口。
type Person interface {Speak()
}
Person就是一个接口类型,包含一个 Speak() 方法。
1.2.1 interface 变量
interface 是一种复合类型,既然是类型,就能声明变量
var p Person
声明的变量 p 没有赋值,其值为 nil
p 能存储什么值 ?
1.2.2 实现接口
在Java或者c/c++中,实现接口需要使用特定的关键字,声明实现的接口。
在Go里面有一个鸭子类型的思想:
“鸭子类型”(Duck Typing)是一种编程概念,它的名字来源于英语中的一句谚语:
“如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子”。
在编程中,鸭子类型的思想是:
一个对象的类型不是由它从哪个类继承或者它实现了哪些接口来决定的,
而是由它有哪些行为(即方法)来决定的。
在 Go 语言中,鸭子类型的概念主要通过接口(interface)来实现的。
在 Go 中,接口是一种类型,它定义了一组方法,但是没有实现这些方法。
任何实现了接口中所有方法的类型都被认为实现了该接口,无论这个类型的定义在哪里,
都不需要显式地声明这个类型实现了这个接口。
比如
type Man struct{Name string
}
func (m Man)Speak() {fmt.Println("hello , I'am " + m.Name)
}
此时结构体 Man 就实现了 Speak 方法
那么 结构体 Man 的变量就可以存储到 Person 变量中
var p Person
var m Man
p = m
interface 变量可以存储任意实现了该接口类型的变量
1.2.3 复合类型
interface 类型的变量在存储某个变量时,会同时保存变量类型和变量值:
tab 保存变量类型以及方法集
data 保存变量的指针
interface 变量同时保存变量值和变量类型。
1.2.4 空 interface
空 interface 是一种非常特殊的 interface 类型,没有指定任何方法集,因此,
任意类型都可以认为实现了 空 interface ,那么 空 interface 也就可以存储任意值
有点像 Java 里面 的 Object 类
2. 反射定律
interface 类型有一个 (value,type) 对,反射就是操作 interface 的 (value, type )的机制。
换句话来说,就是Go 提供方法来获取 interface 的 value 和 type.
2.1 reflect 包
反射包,reflect 包提供了reflect.Type
和reflect.Value
两种类型,用于表示 interface 中的 value 和 type.
同时 reflect 包提供了两个方法用于获取 interface 的 value 和 type
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
reflect.Type 和 reflect.Value 称为反射对象
2.2 反射可以将 interface 类型变量转换为反射对象
func TestRef(t *testing.T) {var x float64 = 3.14tf := reflect.TypeOf(x)fmt.Println("type:", tf)vf := reflect.ValueOf(x)fmt.Println("value:", vf)
}
在reflect.TypeOf
和reflect.ValueOf
中,两个函数的入参是 interface 类型
在调用的时候,发生了隐式的类型转换
将 float64 转为了 interface{} 类型
这样就实现了 interface 变量转为 反射对象
这也是反射的开端,拿到了反射对象就可以进行其他的操作了
2.3 反射可以将反射对象还原成 interface 对象
反射对象 与 interface 对象是可以相互转化的。
func TestUnRef(t *testing.T) {var A interface{}A = 100vo := reflect.ValueOf(A)B := vo.Interface()if A == B {fmt.Println("same")}
}
通过 reflect.ValueOf 获取接口变量 A 的反射对象,然后又通过反射对象的 Interface 获取 B ,最终 A 和 B 是相同的。
2.4 反射对象可修改,value 值必须是可设置的
通过反射可以将 interface 类型的变量转换为反射对象,可以使用该反射对象设置 interface 变量持有的值。
func TestChgRef(t *testing.T) {var x float64 = 3.14vf := reflect.ValueOf(&x)vf.Elem().SetFloat(4.14)fmt.Println(x)fmt.Println(vf.Elem().Interface())
}
通过reflect.ValueOf
获取 interface 对象的反射对象,然后通过Elem
获取指向 interface 对象的指针
接着就可以通过指针赋值了
在打印的时候,直接打印 interface 对象,发现值已经被修改
打印反射对象通过Interface()
获取的 interface 对象,也就是原对象,值也是修改的。
需要注意函数调用的 传参,引用传递还是值传递
如果是值传递,就是不可修改的,就不能 set
func TestChgRefPanic(t *testing.T) {var x float64 = 3.14vf := reflect.ValueOf(x)vf.SetFloat(4.14)fmt.Println(x)fmt.Println(vf.Interface())
}
因为是值传递,所以在函数内部相当于是复制了一个副本,修改副本的值,并不会同步到原值,相当于是无效的修改,因此就panic了
3. 思考 接口实现和接口继承
如果有如下代码:
type Person interface {Speak()
}
type Man struct {Name string
}
func (m Man)Speak() {fmt.Println(m.Name)
}
type Woman struct{PersonName string
}
func (w Woman)Speak() {fmt.Println(w.Name)
}
那么不管是 Woman 还是 Man 都能调用 Speak 方法,那么有什么区别呢?
从接口实现的角度理解,不管是 Woman 还是 Man 都实现是了 Person 接口。
那么在反射中来说,Person 变量都可以存储 Woman 或者 Man 的变量。
从接口继承的角度理解
Man 只是实现了接口,而 Woman 组合了 Person,这意味着 Woman 可以隐式的拥有一个 Person 类型的变量
在调用的时候,可以调用 Person.Speak 。
简单来说,可以这么理解:
Man 实现 Person
Woman 实现 Person 并且 组合 Person
在方法调用上,Man 只能调用自己实现的 Speak 逻辑
Woman 不仅可以调用自己的逻辑,还可以调用 其他实现的 Speak 逻辑
可以借用 Java 里面 Woman 继承了 A 类,并且实现了 Person 接口,A类实现了 Person 接口,
在方法调用上,可以让 Woman 调用A.Speak