Go的接口
Go的接口定义了一组方法(方法集),但不包含这些方法的具体实现。接口提供了一种方式来说明某类对象具有的行为,它的主要特点如下:
- 接口实际上就是一组方法声明的集合,没有具体实现,没有字段属性
- 某个类型只要实现了某个接口的所有方法,就实现了该接口,不需要显示声明实现了什么接口
- 接口可以嵌入其它的接口
接口的定义和使用
定义接口的形式:
type InterfaceName interface {
Method1(param_list) return_type
Method2(param_list) return_type
}
下面是一个接口使用的实例,定义了一个Shaper
的接口,它包含了Area()
和Perimeter
两个方法;再定义了一个Square
类型,它实现了上述具体两个方法,从而继承了接口。在主程序中,就可以创建一个Square
类型的变量赋值给接口Shaper
//定义接口 type Shaper interface { Area() float64 Perimeter() float64 } //定义类型正方形 type Square struct { side float64 } //实现接口中的方法Area func (sq Square) Area() float64 { return sq.side * sq.side } //实现接口中的方法Perimeter func (sq Square) Perimeter() float64 { return sq.side * 4 } func main() { var sq Shaper sq = Square{ side: 2, } fmt.Println(sq.Area()) }
要注意的是,当对象赋值给接口时(sq=Square{side:2,}
),会对对象进行拷贝,再赋值给接口,接口中保存的是指向这个拷贝的地址指针。为了性能考虑,我们也可以将对象的指针赋值给接口,这样就不需要拷贝整个原始对象,只拷贝对象地址,地址还是指向原来的对象。上面的代码可以改成:
//定义接口 type Shaper interface { Area() float64 Perimeter() float64 } //定义类型正方形 type Square struct { side float64 } //实现接口中的方法Area func (sq *Square) Area() float64 { return sq.side * sq.side } //实现接口中的方法Perimeter func (sq *Square) Perimeter() float64 { return sq.side * 4 } func main() { var sq Shaper sq = &Square{ side: 2, } }
此外,还需要注意到几点:
- 多个类型可以实现同一个接口
- 实现某个接口的类型除了实现接口要求实现的方法外,还可以有其它的方法
- 一个类型可以实现多个接口
在接口中内嵌接口
在Go接口的声明中同样可以嵌入一个接口,实现该外层接口的类型同样需要实现内嵌接口声明的方法
package main import ( "fmt" ) //定义类型正方形 type Square struct { side float64 } type Shaper interface { Area() float64 Perimeter() float64 infoPrint //嵌入接口 } type infoPrint interface { PrintInfo() } //计算面积 func (sq Square) Area() float64 { return sq.side * sq.side } //计算周长 func (sq Square) Perimeter() float64 { return sq.side * 4 } //实现内嵌接口的方法 func (sq Square) PrintInfo() { fmt.Println("我是一个正方形") } func main() { var sq Shaper sq = Square{ side: 2, } sq.PrintInfo() }
接口转换
不同的接口之间可以进行转换,转换的原则是大接口转小接口,即将拥有内嵌子接口的接口转换为内嵌子接口
type Shaper interface { Area() float64 Perimeter() float64 infoPrint //嵌入接口 } type infoPrint interface { PrintInfo() } //省略方法实现 func main(){ var sq Shaper sq = Square{ side: 2, } var ip infoPrint ip = infoPrint(sq) //这里将Shaper转换为inforPrint ip.PrintInfo() }
空接口
如果一个接口中不含任何的方法,那么该接口是个空接口,所有的类型都实现了空接口,它相当于所有类型的基类,类似于Java中的Object
类
type Empty interface {}
可以给一个空接口的变量赋值任何类型
类型断言
一个接口类型的变量中可能包含着不同实际类型的值(实现接口的可以有不同的类型),当我们需要判断接口变量中的实际类型时,可以使用类型断言来检测 假设i
是一个接口变量,T
是某个具体实现该接口的类型,那么可以使用下面的语句来检测i
是否为类型T
:
s, ok := i.(T)
当接口变量i
的实际类型是T
时,s
是i
转换到类型T
的值,ok
的值是true
;当接口变量i
的实际类型不是T
时,s
是类型T
的零值,ok
是false
。下面是一段检测接口类型的实例:
package main import ( "fmt" ) type Shaper interface { Area() float64 Perimeter() float64 } type Square struct { side float64 } //计算面积 func (sq Square) Area() float64 { return sq.side * sq.side } //计算周长 func (sq Square) Perimeter() float64 { return sq.side * 4 } func main() { sq := Square{ side: 2, } isShape(sq) } //判断接口中的数据类型 func isShape(s Shaper) { if sp, ok := s.(Square); ok { fmt.Println(sp, "is a shaper") } else { fmt.Println("none") } }
接口变量的类型也可以用一种type-switch
的形式去检测:
package main import ( "fmt" ) type Shaper interface { Area() float64 Perimeter() float64 } type Square struct { side float64 } func (sq Square) Area() float64 { return sq.side * sq.side } func (sq Square) Perimeter() float64 { return sq.side * 4 } func main() { sq := Square{ side: 2, } whatType(sq) } func whatType(s interface{}) { switch v := s.(type) { case Square: fmt.Println(v, "is a square") case int: fmt.Println(v, "is a square") default: fmt.Println("none") } }
在函数whatType
中,对接口变量的类型进行检测,函数传入的是一个空接口类型的参数,v = s.(type)
得到变量s
的具体类型,然后对case
中列举的类型进行匹配,如果被检测的类型没有在case
语句列举的类型中,就会执行default
语句。但注意type-switch
中不允许使用fallthrough