接口是一种约定,它是一个抽象的类型, 和我们见到的具体的类型如 int 、 map 、 slice 等不一样。 具体的类型, 我们可以知道它是什么, 并且可以知道可以用它做什么; 但是接口不一样, 接口是抽象的, 它只有一组接口方法, 我们并不知道它的内部实现, 所以我们不知道接口是什么, 但是我们知道可以利用它提供的方法做什么。
接口一般这样定义:接口定义一个对象的行为。
这里定义了一个有两个方法的接口I:
type I interface {
Get() int
Put(int)
}
下面定义了具有一个字段和两个方法的结构类型S
type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }
对于接口 I , S 是合法的实现,因为它定义了 I 所需的两个方法。注意,即便是没有明确定义 S 实现了 I ,这也是正确的。 interface
类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则次对象就实现了此接口。
如果我们定义了一个 interface
的变量,那么这个变量里面可以存实现这个 interface
的任意类型的对象。 例如下面例子中,我们定义了一个 Men interface 类型的变量 m ,那么 m 里面可以存 Human 、 Student 或者 Employee 值.例:
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human //匿名字段
school string
loan float32
}
type Employee struct {
Human //匿名字段
company string
money float32
}
//Human实现SayHi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human实现Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone)
}
// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
//定义Men类型的变量i
var i Men
//i能存储Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//i也能存储Employee
i = Tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
//定义了slice Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
//这三个都是不同类型的元素,但是他们实现了interface同一个接口
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x{
value.SayHi()
}
}
interface{}
不包含任何的 method ,它可以存储任意类型的数值。fmt的源码:
type Stringer interface{
String() string
}
任何实现了 String 方法的类型都能作为参数被 fmt.Println
调用如果需要某个类型能fmt包以特殊的格式输出,就必须实现 Stringer 这个接口。例:
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
//通过此方法Human实现了fmt.Stringer
func (h Human) String() string {
retrun h.name + " - " + strconv.Itoa(h.age) + " years - " + h.phone
}
func main(){
Bob := Human{"Bob", 39, "000-7777-xxx"}
fmt.Println("This Human is:", Bob)
}
例:
func f(p I) { //定义一个函数接受一个接口类型作为参数
fmt.Println(p.Get()) //p实现了接口I,必须有Get()方法
p.Put(1) //Put()方法是类似的
}
这里的变量 p 保存了接口类型的值。因为 S 实现了 I,可以调用 f 向其传递 S 类型的值的指针:
var s S
f(&s)
获取 s 的地址,而不是 S 的值的原因,是因为在 s 的指针上定义了方法,参阅上面的代码。这并不是必须的——可以定义让方法接受值——但是这样的话 Put 方法就不会像期望的那样工作了。实际上,无须明确一个类型是否实现了一个接口意味着 Go 实现了叫做 duck typing 的模式。这不是纯粹的 duck typing ,因为如果可能的话 Go 编译器将对类型是否实现了接口进行实现静态检查。
假设需要在函数 f 中知道实际的类型。在 Go 中可以使用 type switch 得到。
func f(p I) {
switch t := p.(type) { //类型判断。在switch语句中使用(type)。保存类型到变量t;
case *S: //p的实际类型是S的指针;
case *R: //p的实际类型是R的指针;
case S: //p的实际类型是S;
case R: //p的实际类型是R;
default: //实现了I的其他类型。
}
}
注意: element.(type)
语法不能在 switch
外的任何逻辑里面使用,如果你要在 switch
外面判断一个类型就使用 comma-ok。
类型判断不是唯一的运行时得到类型的方法。为了在运行时得到类型,同样可以使用 comma, ok
来判断一个接口类型是否实现了某个特定接口:
if t, ok := something.(I); ok {
// 对于某些实现了接口I 的
// t 是其所拥有的类型
}
Go语言里面有一个语法,可以直接判断是否是该类型的变量:
value, ok = element.(T)
这里 value 就是变量的值, ok 是一个 bool 类型, element 是 interface 变量, T 是断言的类型。如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true ,否则返回 false 。例:
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//定义了String方法,实现了fmt.Stringer
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Println("list[%d] is of a different type", index)
}
}
}
输出结果:
list[0] is an int and its value is 1
list[1] is a string and its value is Hello
list[2] is a Person and its value is (name: Dennis - age: 70 years)
确定一个变量实现了某个接口,可以使用:
t := something.(I)
由于每个类型都能匹配到空接口: interface{}
。我们可以创建一个接受空接口作为参数的普通函数:
func g(something interface{}) int {
return something.(I).Get()
}
在这个函数中的 return something.(I).Get()
是有一点窍门的。值 something 具有类型 interface{} ,这意味着方法没有任何约束:它能包含任何类型。 .(I)
是类型断言,用于转换 something 到 I 类型的接口。如果有这个类型,则可以调用 Get() 函数。因此,如果创建一个 *S
类型的新变量,也可以调用 g() ,因为 *S
同样实现了空接口。
s = new(S)
fmt.Println(g(s));
例:
package main
import (
"fmt"
)
func main(){
s := new(S)
ss := "Hello world"
fmt.Printf("%d\n",g(s))
fmt.Printf("%s\n",demo(ss))
}
func g(something interface{}) int {
return something.(I).Get()
}
func demo(something interface{}) string {
return something.(string)
}
type I interface {
Get() int
Put(int)
}
type S struct { i int }
func (p *S) Get() int {
return p.i
}
func (p *S) Put(v int) {
p.i = v
}
输出结果:
0
Hello world
例2:
package main
import (
"fmt"
)
func main(){
s := S{1}
fmt.Printf("%d\n",g(s))
}
func g(something S) int {
return something.Get()
}
type S struct { i int }
func (p *S) Get() int {
return p.i
}
func (p *S) Put(v int) {
p.i = v
}
输出结果:1
根据规则,单方法接口命名为方法名加上 er
后缀:如 Reader
, Writer
, Formatter
等。有一堆这样的命名,高效的反映了它们职责和包含的函数名。 Read
, Write
, Close
, Flush
, String
等等有着规范的声明和含义。为了避免混淆,除非有类似的声明和含义,否则不要让方法与这些重名。相反的,如果类型实现了与众所周知的类型相同的方法,那么就用相同的名字和声明;将字符串转换方法命名为 String 而不是 ToString 。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有