接口值
从概念来说,一个接口类型的值(简称接口值)其实有两部分:一个具体类型和该类型的一个值。二者称为接口的动态类型和动态值。
golang中,类型仅仅是一个编译时的概念,所以类型不是一个值。在概念模型中,用类型描述符来提供每个类型的具体信息,比如它的名字和方法。- 接口值可以用
==和!=操作符来做比较。如果两个接口值都是nil或者二者的动态类型完全一致且二者动态值相等(使用动态类型的==操作符来做比较),那么两个接口值相等。因为接口值是可以比较的,所以它们可以作为map的键,也可以作为switch语句的操作数。 - 在比较两个接口值时,如果两个接口值的动态类型一致,但对应的动态值是不可比较的,那么这个比较会以崩溃的方式失败。从这点看,接口类型是非平凡的。
注意:含有空指针的非空接口
空的接口值与仅仅动态值为nil的接口值是不一样的。
使用sort.Interface来排序
sort.Interface接口指定通用排序算法和每个具体的序列类型之间的协议。这个接口的实现确定了序列的具体布局(经常是一个slice),以及元素期望的排序方式。
- 一个原地排序算法需要知道三个信息:序列长度、比较两个元素的含义以及如何交换两个元素,所以
sort.Interface接口就有三个办法。
package sort
type Interface interface {
Len() int
Less(i, j int) bool // i, j是序列元素的下标
Swap(i, j int)
}
要对序列排序,需要先确定一个实现了如上三个方法的类型,接着把sort.Sort函数应用到上面这类方法的实例上。
sort.Reverse使用了一个重要概念:组合。
package sort
type reverse struct { Interface } // that is, sort.Interface
func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
func Reverse(data Interface) Interface { return reverse{ data } }
sort包定义了一个未导出的类型reverse,这个类型是一个嵌入了sort.Interface的结构。reverse的Less方法直接调用了内嵌的sort.Interface值的Less方法,但只交换传入的下标,就可以颠倒排序的结果。
reverse的另外两个方法Len和Swap,由内嵌的sort.Interface隐式提供。导出的函数Reverse则返回一个包含原始sort.Interface值的reverse实例。
http.Handler接口
作为服务端API基础的http.Handler接口。
package http
type Handler interface {
ServeHTTP(W ResponseWriter, r *Request)
}
func ListenAndServe(address string, h Handler) error
ListenAndServe函数需要一个服务器地址,比如"localhost:8000",以及一个Handler接口的实例。
- 普通处理
URL
// req *http.Request
req.URL.Path
- 普通地处理请求参数
// /xxx?item=value
switch req.URL.Path{
case "/xxx":
item := req.URL.Query().Get("item")
/*
xxx
*/
}
URL的Query方法,把Http的请求参数解析为map,或者更精确来讲,解析为一个multimap,由net/url包的url.Values类型实现。
net/http包提供了一个请求多工转发器ServeMux,用来简化URL和处理程序之间的关联。- 满足同一个接口的多个类型是可以互相替代的,
Web服务器可以把请求分发到任意一个http.Handler。 - 对于一个更复杂的应用,多个
ServeMux会组合起来,用来处理更复杂的分发需求。
ServeMux例子
mux := http.NewServeMux()
mux.Handle(URL1 string, http.HandlerFunc(mothod.func1))
max.Handle(URL2 string, http.HandlerFunc(mothod.func2))
log.Fatal(http.ListenAndServe(ip_port string, mux))
func (md mothod) func1(w http.ResponseWriter, req *http.Request) {
/*
xxx
*/
}
func (md mothod) func2(w http.ResponseWriter, req *http.Request) {
/*
xxx
*/
}
表达式http.HandlerFunc(xxx.func)其实是类型转换,而不是函数调用。注意,http.HandlerFunc是一个类型。
package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
net/http包提供一个全局的ServeMux实例DefaultServeMux,以及包级别的注册函数http.Handle和http.HandlerFunc。要让DefaultServeMux作为服务器的主处理程序,无须把它传给ListenAndServe,直接传nil即可。
error接口
这只是一个接口类型。
type error interface {
Error() string
}
构造error最简单的方法是调用errors.New,它会返回一个包含指定的错误消息的新error实例。
- 完整的
error包只有如下4行代码。
package errors
func New(text string) error { return &errorString{ text } }
type errorString struct { text string }
func (e *errorString) Error() string { return e.text }
- 底层的
errorString类型是一个结构,而没有直接用字符串,主要是为了避免将来无意间的布局变更。满足error接口的是*errorString指针,而不是原始的errorString,主要是为了让每次New分配的error实例都互不相等。
fmt.Println(errors.New("EOF") == errors.New("EOF")) // false
- 直接调用
errors.New比较罕见,因为有一个更易用的封装函数fmt.Errorf,它还额外提供了字符串格式化功能。
package fmt
import "errors"
func Errorf(format string, args ...interface{}) error {
return errors.New(Sprintf(format, args...))
}
类型断言
类型断言是一个作用在接口值上的操作,写出来类似于x.(T),其中x是一个接口类型的表达式,而T是一个类型(称为断言类型)。类型断言会检查作为操作数的动态类型是否满足指定得断言类型。如果断言类型T是一个具体类型,那么类型断言会检查x的动态类型是否就是T。
- 如果断言类型
T是一个接口类型,那么类型断言检查x的动态类型是否满足T。如果检查成功,动态值并没有提取出来,结果仍然是一个接口值,接口值的类型和值部分也没有变更,只是结果的类型为接口类型T。 - 如果类型断言出现在需要两个结果的赋值表达式中,那么断言不会在失败时崩溃,而是会多返回一个布尔型的返回值来指示断言是否成功。
if w, ok := w.(*os.File); ok {
// ...use w...
}
返回值的名字与操作数变量名一致,原有的值就被新的返回值掩盖了。
使用类型断言来识别错误
os包中有三类原因通常必须单独处理:文件已存储(创建操作),文件没找到(读取操作)以及权限不足。
package os
func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool
通过接口类型断言来查询特征
给一个特定类型多定义一个方法,就隐式地接受了一个特定约定。
类型分支
接口有两种不同的风格。
- 接口上的各种方法突出了满足这个接口的具体类型之间的相似性,但隐藏了各个具体类型的布局和各自特有的功能。这种风格强调了方法,而不是具体类型。
- 充分利用·了接口值能够容纳各种具体类型的能力,它把接口作为这些类型的联合(
union)来使用。这种风格的接口使用方式称为可识别联合。