Go 的 interface,跟我一开始理解的不太一样

举报
yd_241780069 发表于 2026/11/05 21:15:45 2026/11/05
【摘要】 起因写 Java 写了几年,习惯了 implements 关键字,类上不写这句就不算实现接口。转 Go 之后,照着习惯找“在哪声明实现了接口”,找了半天没找到,以为哪里写错了。后来才知道,Go 的接口是隐式实现的。你不需要告诉编译器“我这个类实现了那个接口”,只要方法签名对上了,它就自动算实现。 显式和隐式Java 里是这样:public class UserService impleme...

起因

写 Java 写了几年,习惯了 implements 关键字,类上不写这句就不算实现接口。转 Go 之后,照着习惯找“在哪声明实现了接口”,找了半天没找到,以为哪里写错了。

后来才知道,Go 的接口是隐式实现的。你不需要告诉编译器“我这个类实现了那个接口”,只要方法签名对上了,它就自动算实现。

显式和隐式

Java 里是这样:

public class UserService implements UserRepo {
    // 必须显式声明
}

Go 里什么都不用写:

type UserRepo interface {
    FindByID(id int64) (*User, error)
}

type userRepo struct {
    db *sql.DB
}

func (r *userRepo) FindByID(id int64) (*User, error) {
    // 实现逻辑
}

userRepo 没有一行代码提到 UserRepo,但它就是实现了。只要 FindByID 的方法签名和接口里的一致,var repo UserRepo = &userRepo{} 就能通过编译。

好处是解耦更彻底。接口定义方不需要知道实现方是谁,实现方也不需要引用接口所在的包。标准库里的 io.Writer、http.Handler 都是这么工作的,你的类型不需要 import io 包就能被 fmt.Fprintf 使用。

坏处是编译器报错的时候不够直观。方法名拼错一个字母,或者参数类型差一点,编译报的是“userRepo does not implement UserRepo (missing method FindByID)”,你得自己去对方法签名,不像 Java 那样直接告诉你“没写 implements”。

空接口和 any

老代码里经常看到 interface{},Go 1.18 之后有了 any,是它的别名,写起来短一点。

空接口没有任何方法约束,所以所有类型都实现了空接口。这就是为什么 fmt.Println 能接受任何参数,它的签名是 Println(a ...any)。

但 any 用多了类型就丢了,取值的时候得做类型断言或者 switch v := x.(type)。我现在只在确实需要接受任意类型的地方用,比如写通用工具函数,业务代码里能定具体类型就定具体类型。

小接口比大接口好

Go 社区有个说法:接口越大,抽象越弱。

标准库里的 io.Reader 只有一个 Read 方法,io.Writer 只有一个 Write 方法。正因为它们够小,几乎任何东西都能实现,os.File、bytes.Buffer、网络连接、压缩流,全都能塞进 io.Reader 里用。如果当初定义成十个方法的接口,能实现它的类型就少得多了。

我之前写过一个 Repository 接口,把增删改查全塞进去了,后来发现某个场景只需要查,但测试的时候得把删改的方法全 mock 一遍。拆成 UserReader、UserWriter 两个小接口之后,测试只 mock 需要的那一个,清爽很多。

一个实际撞过的坑

接口变量的零值是 nil,但装了一个 nil 具体值的接口变量,不等于 nil。

func do() error {
    var p *MyError = nil
    return p // 返回的 error 不是 nil
}

if err := do(); err != nil {
    // 这里会进来,但 p 本身是 nil
}

error 是个接口,p 虽然指向 nil,但接口变量里存了类型信息(*MyError),所以接口整体不等于 nil。这个坑排查起来很烦,因为打印出来是 <nil>,看着像 nil 但不是。

解决办法要么返回具体的 nil,要么用 errors.Is 判断底层值,别直接 == nil 比较接口。我现在的习惯是,返回接口类型的时候,尽量别在函数内部声明具体类型的 nil 指针再返回。

小结

Go 的接口隐式实现,写起来轻,但报错得自己看方法签名。小接口是正道,any 少用。接口和 nil 的比较那个坑,踩过一次就记住了。

先记这些。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。