什么是接口
在 Golang 中,一个接口是一组方法签名。当一个类型定义了接口里所有定义的方法时,就说这个类型实现了这个接口。接口指定类型应该具有的方法,类型决定如何实现这些方法。
声明和实现一个接口
package main
import (
"fmt"
)
//interface definition
type VowelsFinder interface {
FindVowels() []rune
}
type MyString string
//MyString implements VowelsFinder
func (ms MyString) FindVowels() []rune {
var vowels []rune
for _, rune := range ms {
if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
vowels = append(vowels, rune)
}
}
return vowels
}
func main() {
name := MyString("Sam Anderson")
var v VowelsFinder
v = name // possible since MyString implements VowelsFinder
fmt.Printf("Vowels are %c", v.FindVowels())
}
在上面的程序中,创建了一个接口类型名为 VowelsFinder
,它有一个方法 FindVowels() []rune
。
通过添加一个方法 FindVowels() []rune
给接受者类型 MyString
。现在可以说 MyString
实现了 VowelsFinder
接口。在 Golang 中,如果一个类型包含了一个接口声明的所有方法,那么这个类型就隐式地实现了这个接口。
将 MyString
类型的变量 name
赋值给 VowelsFinder
类型的变量 v
。这是合法的,因为 MyString
实现了 VowelsFinder
。v.FindVowels()
在 MyString
上调用 FindVowels
方法打印字符串 Sam Anderson
中所有的元音。
空接口
一个没有声明任何方法的接口称为空接口。空接口表示为 interface{}
。因为空接口没有方法,因此所有类型都实现了空接口。
package main
import (
"fmt"
)
func describe(i interface{}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
func main() {
s := "Hello World"
describe(s)
i := 55
describe(i)
strt := struct {
name string
}{
name: "Naveen R",
}
describe(strt)
}
上面的程序,describe(i interface{})
函数接受一个空接口作为参数,因此任何值都可以传递给它。
接口内部表示
一个接口可以被认为是由一个元组内部表示的 (type, value)
。type
是接口的具体类型,value
是具体类型的值。
package main
import (
"fmt"
)
type Test interface {
Tester()
}
type MyFloat float64
func (m MyFloat) Tester() {
fmt.Println(m)
}
func describe(t Test) {
fmt.Printf("Interface type %T value %v\n", t, t)
}
func main() {
var t Test
f := MyFloat(89.7)
t = f
describe(t)
t.Tester()
}
Test
接口提供了一个方法 Tester()
,MyFloat
类型实现了这个接口。将 MyFloat
类型的变量 f
赋值给 Test
类型的变量 t
。现在 t
的具体类型是 MyFloat
而它的值是 89.7。
接口的零值
接口的零值是 nil
。一个 nil
接口的底层类型和值都是 nil
。
package main
import "fmt"
type Describer interface {
Describe()
}
func main() {
var d1 Describer
if d1 == nil {
fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
}
}
如果试图在一个 nil
接口上调用方法,程序将会触发 panic
,因为 nil
接口既没有底层的值,也没有具体的类型。
package main
type Describer interface {
Describe()
}
func main() {
var d1 Describer
d1.Describe()
}
上面的程序,因为 d1
是 nil
。程序将在运行时触发 panic
:runtime error: invalid memory address or nil pointer dereference
。
实现多个接口
一个类型可以实现多个接口。
package main
import (
"fmt"
)
type SalaryCalculator interface {
DisplaySalary()
}
type LeaveCalculator interface {
CalculateLeavesLeft() int
}
type Employee struct {
firstName string
lastName string
basicPay int
pf int
totalLeaves int
leavesTaken int
}
func (e Employee) DisplaySalary() {
fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}
func (e Employee) CalculateLeavesLeft() int {
return e.totalLeaves - e.leavesTaken
}
func main() {
e := Employee {
firstName: "Naveen",
lastName: "Ramanathan",
basicPay: 5000,
pf: 200,
totalLeaves: 30,
leavesTaken: 5,
}
var s SalaryCalculator = e
s.DisplaySalary()
var l LeaveCalculator = e
fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())
}
嵌入接口
尽管 Golang 不提供继承,但是可以通过嵌入其他接口来创建新的接口。
package main
import (
"fmt"
)
type SalaryCalculator interface {
DisplaySalary()
}
type LeaveCalculator interface {
CalculateLeavesLeft() int
}
type EmployeeOperations interface {
SalaryCalculator
LeaveCalculator
}
type Employee struct {
firstName string
lastName string
basicPay int
pf int
totalLeaves int
leavesTaken int
}
func (e Employee) DisplaySalary() {
fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}
func (e Employee) CalculateLeavesLeft() int {
return e.totalLeaves - e.leavesTaken
}
func main() {
e := Employee {
firstName: "Naveen",
lastName: "Ramanathan",
basicPay: 5000,
pf: 200,
totalLeaves: 30,
leavesTaken: 5,
}
var empOp EmployeeOperations = e
empOp.DisplaySalary()
fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())
}
类型断言
类型断言用来提取接口的实际类型的值。
语法:i.(T)
,用来获取接口 i
的实际类型 T
的值。
package main
import (
"fmt"
)
func assert(i interface{}) {
s := i.(int) //get the underlying int value from i
fmt.Println(s)
}
func main() {
var s interface{} = 56
assert(s)
}
变量 s
的实际类型是 int
。使用 i.(int)
来获取 i
的 int
值。
如果实际类型不是 int
,那么上面的程序会发生什么?
package main
import (
"fmt"
)
func assert(i interface{}) {
s := i.(int)
fmt.Println(s)
}
func main() {
var s interface{} = "Steven Paul"
assert(s)
}
在上面的程序中,将实际类型为 string
的变量 s
传递给 assert
函数,assert
函数尝试从其中提取出一个 int
值。该程序会触发 panic
:interface conversion: interface {} is string, not int
。
为了解决以上问题,可以使用下面的语法:
v, ok := i.(T)
如果接口 i
的具体类型是 T
,则 v
将具有 i
的实际值,ok
为 true。
如果接口 i
的具体类型不是 T
,则 ok
为 false, v
为 T
的零值,但程序不会触发 panic
。
package main
import (
"fmt"
)
func assert(i interface{}) {
v, ok := i.(int)
fmt.Println(v, ok)
}
func main() {
var s interface{} = 56
assert(s)
var i interface{} = "Steven Paul"
assert(i)
}
当把字符串 Steven Paul
传递给 assert
函数,ok
将是 false,因为 i
的实际类型不是 int
,v
的值将是 0(int
的零值)。
Type Switch
类型分支(type switch)用来将一个接口的具体类型与多个 case
语句指定的类型进行比较。在类型断言 i.(T)
中,将类型 T
替换为关键字 type
就变成了 type switch。
package main
import (
"fmt"
)
func findType(i interface{}) {
switch i.(type) {
case string:
fmt.Printf("I am a string and my value is %s\n", i.(string))
case int:
fmt.Printf("I am an int and my value is %d\n", i.(int))
default:
fmt.Printf("Unknown type\n")
}
}
func main() {
findType("Naveen")
findType(77)
findType(89.98)
}
也可以将类型与接口进行比较。如果我们有一个类型,并且如果这个类型实现了一个接口,就可以将这个类型与它实现的接口进行比较。
package main
import "fmt"
type Describer interface {
Describe()
}
type Person struct {
name string
age int
}
func (p Person) Describe() {
fmt.Printf("%s is %d years old", p.name, p.age)
}
func findType(i interface{}) {
switch v := i.(type) {
case Describer:
v.Describe()
default:
fmt.Printf("unknown type\n")
}
}
func main() {
findType("Naveen")
p := Person{
name: "Naveen R",
age: 25,
}
findType(p)
}
网友评论