8.3 内置接口
为了帮助开发者提高开发效率,Go语言内置了一些实现常用功能的接口。在本节的内容中,将详细讲解几个常用的内置接口。
8.3.1 使用接口error处理错误
错误处理在每个编程语言中都是一项重要的内容之一,在开发中通常会遇到异常与错误这两种信息,Go语言中也不例外。在C语言中通过返回 -1 或者 NULL 之类的信息来表示错误,但是对于使用者来说,如果不查看相应的 API 说明文档,不清楚这个返回值究竟代表什么意思,比如返回 0 是成功还是失败?
针对这样的情况,Go语言引入了 error 接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含 error。error 处理过程类似于C语言中的错误码,可逐层返回,直到被处理。
查看Go语言的源码时会发现 ,error 类型是一个非常简单的接口类型,代码如下所示:
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
在接口error中有一个签名为 Error() string 的方法,所有实现该接口的类型都可以当作一个错误类型。Error() 方法给出了错误的描述,在使用 fmt.Println()打印错误信息时,会在内部调用方法Error() string得到该错误的描述。
在一般情况下,如果函数需要返回错误,就将 error 作为多个返回值中的最后一个(但这并非是强制要求)。例如下面的代码(源码路径:Go-codes\8\cuo.go)演示了在函数中使用error接口返回错误信息的过程。
import (
"errors"
"fmt"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divide by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
在上述代码中定义了函数divide(),它接收两个浮点数a和b作为参数,并返回它们的商以及一个错误对象。在函数内部,如果b等于0,则返回一个新的错误对象,该对象包含了一条错误消息"divide by zero"。否则,函数将计算a/b的值,并返回它们的商和一个空的错误对象。在主函数main()中调用divide,并检查其返回的错误对象是否为空。如果错误对象不为空,则说明divide函数执行过程中发生了错误,我们输出错误信息;否则,我们输出计算结果。执行后会输出:
divide by zero
通过这种方式,我们可以使用error接口来返回函数或方法执行过程中的错误信息,并根据需要进行处理。此外,标准库中还有许多实现了error接口的错误类型,比如os.PathError、net.Error等,可以帮助我们更好地处理各种类型的错误。
在Go语言中,还可以使用 error 接口自定义一个 Error()方法,来返回自定义的错误信息。
实例8-4:检查程序中的错误(源码路径:Go-codes\8\ziding.go)
实例文件ziding.go的具体实现流程如下所示。
(1)定义一个自定义的error接口类型,并在该类型上定义了三个不同级别的错误.代码如下所示:
// 定义一个自定义的error接口类型
type CustomError interface {
error
Severity() int
}
// 定义三个不同级别的错误
type LowError struct {
message string
}
func (e LowError) Error() string {
return e.message
}
func (e LowError) Severity() int {
return 1
}
type MediumError struct {
message string
}
func (e MediumError) Error() string {
return e.message
}
func (e MediumError) Severity() int {
return 2
}
type HighError struct {
message string
}
func (e HighError) Error() string {
return e.message
}
func (e HighError) Severity() int {
return 3
}
在上述代码中定义了一个CustomError接口类型,并在其上面定义了Severity()方法,用于返回错误的级别。然后定义了三个不同级别的错误:LowError、MediumError和HighError,并在它们上面分别实现了方法Error()和方法Severity()。
low error: empty data
medium error: data too short
high error: data contains error
(2)编写函数processData(),模拟处理输入数据时可能发生的三种错误。对应代码如下所示。
func processData(data []byte) error {
if len(data) == 0 {
return LowError{"empty data"}
}
if len(data) < 10 {
return MediumError{"data too short"}
}
if bytes.Contains(data, []byte("error")) {
return HighError{"data contains error"}
}
return nil
}
在函数processData()中,首先检查输入数据是否为空。如果是空的,则返回一个LowError级别的错误;否则,继续检查输入数据的长度是否小于10。如果太短,则返回一个MediumError级别的错误;否则,我们继续检查输入数据中是否包含字符串"error"。如果包含,则返回一个HighError级别的错误;否则,返回nil表示没有发生错误。
(3)最后定义主函数main()来调用上面的函数processData(),并根据不同级别的错误执行不同的操作。对应代码如下所示。
func main() {
// 模拟处理三种不同的输入数据,并输出相应的结果
for _, data := range [][]byte{nil, []byte("abc"), []byte("this is an error")} {
err := processData(data)
switch err.(type) {
case LowError:
fmt.Println("low error:", err)
case MediumError:
fmt.Println("medium error:", err)
case HighError:
fmt.Println("high error:", err)
default:
fmt.Println("no error")
}
}
}
在这个例子中,我们模拟处理了三种不同类型的输入数据(一个空的数组、一个长度小于10的数组和一个包含"error"字符串的数组),并使用类型分支来检查函数processData()返回的错误级别。如果返回的错误是LowError,则输出"low error:"并打印错误信息;如果是MediumError,则输出"medium error:"并打印错误信息;如果是HighError,则输出"high error:"并打印错误信息。如果没有发生错误,则输出"no error"。
8.3.2 使用接口sort.Interface实现排序
在Go语言中,在包sort中提供了对切片排序的函数和类型。要对一个切片进行排序,首先需要实现sort.Interface接口。在接口sort.Interface中包含如下三个方法:
- Len() int:返回该切片的长度。
- Less(i, j int) bool:根据索引i和j指定的元素比较它们的顺序,并返回一个布尔值来表示i是否应该排在j前面。
- Swap(i, j int):交换索引i和j指定的元素。
实例8-5:排序学生的年龄(源码路径:Go-codes\8\sort.go)
实例文件sort.go的具体实现代码如下所示。
import (
"fmt"
"sort"
)
// 定义一个Person结构体类型
type Person struct {
Name string
Age int
}
// 定义一个PersonSlice类型,包含多个Person对象,并实现sort.Interface接口
type PersonSlice []Person
func (p PersonSlice) Len() int {
return len(p)
}
func (p PersonSlice) Less(i, j int) bool {
return p[i].Age < p[j].Age
}
func (p PersonSlice) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
// 模拟一个人员名单,并对其进行排序
func main() {
// 创建一个包含多个Person对象的切片
people := PersonSlice{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20},
}
// 对该切片进行排序
sort.Sort(people)
// 输出排序后的结果
for _, person := range people {
fmt.Println(person.Name, person.Age)
}
}
对上述代码的具体说明如下:
- 首先定义了结构体类型Person,并在该类型上定义了方法Less(),用于按照年龄从小到大的顺序比较两个Person对象。然后定义了一个PersonSlice类型,包含多个Person对象,并实现了接口sort.Interface的三个方法。这样,就可以对该切片进行排序了。
- 在主函数main()中创建了一个包含多个Person对象的切片,并使用函数sort.Sort()对其进行排序。通过这种方式,可以方便地对任何实现了sort.Interface接口的切片进行排序,而不需要直接操作底层数据。
执行后会输出:
Charlie 20
Alice 25
Bob 30