接口与类型
go是静态类型的语言。每个变量都有一种静态类型。换言之,他们的类型在 编译期就确定并且固定下来了。
type MyInt
var a int
var b MyInt
那么 i 的类型为 int ,而 j 的类型为 MyInt 。尽管变量 i 和 j 拥有相同的基本类型, 但它们的静态类型仍然不同,因此在它们未经转换前是不能相互赋值的。 在类型中,有一种重要的类别就是接口类型,它表示一个确定的方法集。只要某个具体值 (非接口)实现了某个接口中的方法,该接口类型的变量就能存储它。
接口的表示
接口类型的变量存储了一对内容:赋予该变量的具体值,以及该值的类型描述符。 更准确地说,其值是实现了该接口的具体数据条目,而其类型则描述了该条目的完整类型。 例如,在执行
var r io.Reader
tty, _ := os.OpenFile("/dev/tty", os.O_RDWR, 0)
r = tty
之后,r 就包含了 (值, 类型) 对,即 ( tty, *os.File )。注意,类型 *os.File 还实现了除 Read 以外的其它方法:尽管该接口值只提供了访问 Read 方法的能力,但其内部却携带了有关该值的所有类型信息。 这就是我们可以这样做的原因:
var w io.Writer
w = r.(io.Writer)
在此赋值语句中表达式是一个类型断言: 它断言r内的条目同时实现了io.Writer, 因此我们可以将它赋予w。 w 将会包含一对 ( tty , *os.File )。 这与保存在 r 中的一致。接口的静态类型决定了哪些方法可通过接口变量调用, 即便其内部具体的值可能有更大的方法集。
一个很重要的细节,就是接口内部的对总是 (值, 具体类型) 的形式,而不会是 (值, 接口类型) 的形式。接口不能保存接口值。
三条反射法则
1.反射是从接口值到反射对象
反射只是一种检查存储在接口变量中的"类型-值"的机制,首先,我们需要了解 reflect 包中的两中类型: Type 和 Value,这两种类型可用来访问接口变量的内容。 还有两个简单的函数,叫做 reflect.TypeOf 和 reflect.ValueOf , 它们用于接口值中分别获取 reflect.Type 和 reflect.Value 。 (同样,从 reflect.Value 也能很容易地获取 reflect.Type , 不过让我们先保持 Value 和 Type 概念的独立性吧。
package main
import ( "fmt" "reflect" )
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
当我们调用 reflect.TypeOf(x) 时, x 首先会被存储在一个空接口中, 然后它会作为实参被传入; reflect.TypeOf 通过解包该空接口来还原其类型信息。
reflect.Type 和 reflect.Value 都有许多方法来让我们检测并操作它们。 一个重要的例子就是 Value 拥有一个 Type 方法,它会返回 reflect.Value 的 Type 。另外就是 Type 和 Value 都有一个 Kind 方法,它会返回一个常量来表明条目的类型: Uint 、 Float64 或 Slice 等等。
反射对象的 Kind 描述了其基本类型,而给静态类型。 若反射对象包含了用户定义的整数类型的值,比如
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
那么 v 的 Kind 仍为 reflect.Int ,尽管 x 的静态类型为 MyInt 而非 int 。换句话说, Kind 无法区分 int 和 MyInt ,而 Type 则可以。
2.从反射对象可反射出接口值。
通过v.Interface() 可以还原其原接口值
3.设置值
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
目前会输出
type of p: *float64
settability of p: false
反射对象 p 并不是可设置的,不过我们也不想设置 p , 而(实际上)是 *p 。为获得 p 指向的内容,我们调用 Value 的 Elem 方法,它会间接通过指针,并将结构保存到叫做 v 的反射值 Value 中:
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
现在 v 是可设置的反射对象,如输出所示:
settability of v: true
总结
反射法则如下
- 从接口值可反射出反射对象。
- 从反射对象可反射出接口值
- 要修改反射对象,其值必须可设置。