首页 > 代码库 > Golang-interface(一 基本使用)

Golang-interface(一 基本使用)

github: https://github.com/ZhangzheBJUT/blog/blob/master/interface.md 

一 接口概述

假设说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 是静态类型的。每个变量有一个静态的类型,也就是说,有一个已知类型而且在编译时就确定下来了

type MyInt int
var i int
var j MyInt

那么 i 的类型为 int 而 j 的类型为 MyInt。即使变量 i 和 j 有同样的底层类型,它们仍然是有不同的静态类型的。

未经转换是不能相互赋值的。

二 接口类型

在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。

一个接口变量能够存储随意实际值(非接口)。仅仅要这个值实现了接口的方法。

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{},它表示空的方法集合。因为不论什么值都有零个或者多个方法。所以不论什么值都能够满足它。

也有人说 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接口。(能够这样去理解:指针类型的对象函数是可读可写的。非指针类型的对象函数是仅仅读的)

  • 将一个接口赋值给另外一个接口 

    在Go语言中。仅仅要两个接口拥有同样的方法列表(次序不同不要紧),那么它们就等同的,能够相互赋值。 假设A接口的方法列表时接口B的方法列表的子集,那么接口B能够赋值给接口A,可是反过来是不行的,无法通过编译。

五 接口查询

接口查询是否成功,要在执行期才可以确定。他不像接口的赋值,编译器仅仅须要通过静态类型检查就可以推断赋值是否可行。

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来推断接口存储的类型。

小结: 查询接口所指向的对象是否为某个类型的这样的使用方法能够觉得是接口查询的一个特例。接口是对一组类型的公共特性的抽象。所以查询接口与查询详细类型差别好比是以下这两句问话的差别:

你是医生么?
是。
你是莫莫莫

第一句问话查询的是一个群体,是查询接口;而第二个问句已经到了详细的个体,是查询详细类型。

除此之外利用反射也能够进行类型查询,会在反射中做具体介绍。

參考: http://research.swtch.com/interfaces




Golang-interface(一 基本使用)