接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。
一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。
以下面的代码为例:
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
对于一个接口的零值就是它的类型和值的部分都是nil(图7.1)。
第二个语句将一个*os.File类型的值赋给变量w:
w = os.Stdout
这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这类转换不管是显式的还是隐式的,都会刻画出操作到的类型和值。这个接口值的动态类型被设为*os.Stdout指针的类型描述符,它的动态值持有os.Stdout的拷贝;这是一个代表处理标准输出的os.File类型变量的指针(图7.2)。
因为不是直接进行调用,所以编译器必须把代码生成在类型描述符的方法Write上,然后间接调用那个地址。这个调用的接收者是一个接口动态值的拷贝,os.Stdout。
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等(危险!除非知道接口的动态类型是可比较的,否则容易出现panic)
类型断言是一个使用在接口值上的操作。(下面的两种情况均是使用在接口值上的)
语法上它看起来像x.(T)被称为断言类型,这里x表示一个接口的类型和T表示一个类型。一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。
这里有两种可能。第一种,如果断言的类型T是一个具体类型,然后类型断言检查x的动态类型是否和T相同。如果这个检查成功了,类型断言的结果是x的动态值,当然它的类型是T。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。
第二种,如果该接口值同时支持另一个接口,可以实现动态转换。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
如果返回结果只有一个值,转换失败时会panic。如果有两个值,第二个值是表示转换成功与否的boolean变量。
如何判断某种类型是否实现了接口,可以由一个interface{}类型的变量进行承接,如下:
func CheckInterfaceType(a interface{}) bool {
if _, ok := a.(testType); ok {
return true
}
return false
}
另一个例子和sql注入有关:
func listTracks(db sql.DB, artist string, minYear, maxYear int) {
result, err := db.Exec(
"SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?",
artist, minYear, maxYear)
// ...
}
用这种方式构造查询可以帮助避免SQL注入攻击;这种攻击就是对手可以通过利用输入内容中不正确的引文来控制查询语句。
一个类型开关基于这个接口值的动态类型使一个多路分支有效。
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
如果类型判断后的对象值有用,可以这么写(从作用域的角度,它不会覆盖switch外部的x)
switch x := x.(type) { /* ... */ }