若是说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在现在集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到史无前例的高度。 编程
Go语言在编程哲学上是变革派,而不是改良派。这不是由于Go语言有gorountine和channel,而更重要的是由于Go语言的类型系统,更是由于Go语言的接口。Go语言的编程哲学由于有接口而趋于完美。 数组
C++,Java 使用"侵入式"接口,主要表如今实现类须要明确声明本身实现了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程当中一个遭受至关多质疑的特性。
Go语言采用的是“非侵入式接口",Go语言的接口有其独到之处:只要类型T的公开方法彻底知足接口I的要求,就能够把类型T的对象用在须要接口I的地方,所谓类型T的公开方法彻底知足接口I的要求,也便是类型T实现了接口I所规定的一组成员。这种作法的学名叫作Structural Typing,有人也把它看做是一种静态的Duck Typing。 并发
Go 是静态类型的。每个变量有一个静态的类型,也就是说,有一个已知类型而且在编译时就肯定下来了 app
type MyInt int var i int var j MyInt
那么 i 的类型为 int 而 j 的类型为 MyInt。即便变量 i 和 j 有相同的底层类型,它们仍然是有不一样的静态类型的。未经转换是不能相互赋值的。 函数
在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。一个接口变量能够存储任意实际值(非接口),只要这个值实现了接口的方法。 spa
type Reader interface { Read(p []byte) (n int, err os.Error) } // Writer 是包裹了基础 Write 方法的接口。 type Writer interface { Write(p []byte) (n int, err os.Error) } var r io.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer)
有一个事情是必定要明确的,不论 r 保存了什么值,r 的类型老是 io.Reader,Go 是静态类型,而 r 的静态类型是 io.Reader。 指针
接口类型的一个极端重要的例子是空接口:interface{},它表示空的方法集合,因为任何值都有零个或者多个方法,因此任何值均可以知足它。 code
也有人说 Go 的接口是动态类型的,不过这是一种误解。 它们是静态类型的:接口类型的变量老是有着相同的静态类型,这个值老是知足空接口,只是存储在接口变量中的值运行时可能被改变。 对象
对于全部这些都必须严谨的对待,由于反射和接口密切相关。 继承
接口类型的变量存储了两个内容:赋值给变量实际的值和这个值的类型描述。更准确的说,值是底层实现了接口的实际数据项目,而类型描述了这个项目完整的类型。例以下面,
var r io.Reader tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
用模式的形式来表达 r 包含了的是 (value, type) 对,如 (tty, *os.File)。
注意: 类型 *os.File 除了 Read 方法还实现了其余方法:尽管接口值仅仅提供了访问 Read 方法的可能(即经过r 只能访问Read方法),可是内部包含了这个值的完整的类型信息(反射的依据)。
这也就是为何能够这样作:
var w io.Writer w = r.(io.Writer) //接口查询
在这个赋值中的断言是一个类型断言:它断言了 r 内部的条目同时也实现了 io.Writer,所以能够赋值它到 w。在赋值以后,w 将会包含 (tty, *os.File),跟在 r 中保存的一致。
接口的静态类型决定了哪一个方法能够经过接口变量调用,即使内部实际的值可能有一个更大的方法集。
接下来,能够这样作:
var empty interface{} empty = w
而空接口值 e 也将包含一样的 (tty, *os.File)。这很方便:空接口能够保存任何值同时保留关于那个值的全部信息。
注:这里无需类型断言,由于 w 确定知足空接口的。在上面的个例子中,将一个值从 Reader 变为 Writer,因为 Writer 的方法不是 Reader 的子集,因此就必须明确使用类型断言。
一个很重要的细节是接口内部的对老是 (value, 实际类型) 的格式,而不会有 (value, 接口类型) 的格式。接口不能保存接口值。
接口赋值在Go语言中分为两种状况: 1.将对象实例赋值给接口 2.将一个接口赋值给另一个接口
将对象实例赋值给接口
看下面的例子:
package main import ( "fmt" ) type LesssAdder interface { Less(b Integer) bool Add(b Integer) } type Integer int func (a Integer) Less(b Integer) bool { return a < b } func (a *Integer) Add(b Integer) { *a += b } func main() { var a Integer = 1 var b LesssAdder = &a fmt.Println(b) //var c LesssAdder = a //Error:Integer does not implement LesssAdder //(Add method has pointer receiver) }
go语言能够根据下面的函数:
func (a Integer) Less(b Integer) bool
自动生成一个新的Less()方法
func (a *Integer) Less(b Integer) bool
这样,类型*Integer就既存在Less()方法,也存在Add()方法,知足LessAdder接口。 而根据
func (a *Integer) Add(b Integer)】
这个函数没法生成如下成员方法:
func(a Integer) Add(b Integer) { (&a).Add(b) }
由于(&a).Add()改变的只是函数参数a,对外部实际要操做的对象并没有影响(值传递),这不符合用户的预期。因此Go语言不会自动为其生成该函数。所以类型Integer只存在Less()方法,缺乏Add()方法,不知足LessAddr接口。(能够这样去理解:指针类型的对象函数是可读可写的,非指针类型的对象函数是只读的)
接口查询是否成功,要在运行期才可以肯定。他不像接口的赋值,编译器只须要经过静态类型检查便可判断赋值是否可行。
var file1 Writer = ... if file5,ok := file1.(two.IStream);ok { ... }
这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,若是实现了,则执行特定的代码。
在Go语言中,你能够询问它指向的对象是不是某个类型,好比,
var file1 Writer = ... if file6,ok := file1.(*File);ok { ... }
这个if语句判断file1接口指向的对象实例是不是*File类型,若是是则执行特定的代码。
slice := make([]int, 0) slice = append(slice, 1, 2, 3) var I interface{} = slice if res, ok := I.([]int);ok { fmt.Println(res) //[1 2 3] }
这个if语句判断接口I所指向的对象是不是[]int类型,若是是的话输出切片中的元素。
func Sort(array interface{}, traveser Traveser) error { if array == nil { return errors.New("nil pointer") } var length int //数组的长度 switch array.(type) { case []int: length = len(array.([]int)) case []string: length = len(array.([]string)) case []float32: length = len(array.([]float32)) default: return errors.New("error type") } if length == 0 { return errors.New("len is zero.") } traveser(array) return nil }
经过使用.(type)方法能够利用switch来判断接口存储的类型。
小结: 查询接口所指向的对象是否为某个类型的这种用法能够认为是接口查询的一个特例。接口是对一组类型的公共特性的抽象,因此查询接口与查询具体类型区别比如是下面这两句问话的区别:
你是医生么?
是。
你是莫莫莫
是
第一句问话查询的是一个群体,是查询接口;而第二个问句已经到了具体的个体,是查询具体类型。
除此以外利用反射也能够进行类型查询,会在反射中作详细介绍。