在 Go 中,一个指定了零个方法的接口类型被称为空接口,即 interface{}。随着 Go 1.18 的发布,预声明的类型 any 成为了空接口的别名;因此,所有的 interface{} 都可以被 any 替换。在许多情况下,any 可以被视为过度泛化;正如 Rob Pike 所提到的,它没有传达任何信息(www.youtube.com/watch?v=PAA…)。让我们首先回顾一下核心概念,然后我们可以讨论潜在的问题。
any 类型可以持有任何值类型:
func main() {
var i any
i = 42 // An int
i = "foo" // A string
i = struct{ // A struct
s string
}{
s: "bar",
}
i = f // A function
_ = i // 将值赋给空白标识符,以便示例能够编译
}
func f() {}
在给 any 类型赋值时,我们失去了所有类型信息,这就需要使用类型断言来从 i 变量中获取任何有用的信息,就像前面的示例一样。让我们看另一个使用 any 不准确的示例。在以下示例中,我们实现了一个 Store 结构体和两个方法 Get 和 Set 的框架。我们使用这些方法来存储不同的结构体类型,Customer 和 Contract:
package store
type Customer struct{
// Some fields
}
type Contract struct{
// Some fields
}
type Store struct{}
func (s *Store) Get(id string) (any, error) { // Returns any
// ...
}
func (s *Store) Set(id string, v any) error { // Accepts any
// ...
}
尽管从编译的角度来看 Store 没有错误,我们应该花一点时间思考一下方法签名。因为我们接受并返回 any 类型的参数,这些方法缺乏表达力。如果未来的开发者需要使用 Store 结构体,他们可能不得不深入文档或阅读代码来理解如何使用这些方法。因此,接受或返回 any 类型并不能传达有意义的信息。此外,由于在编译时没有保护措施,没有什么可以阻止调用者使用任何数据类型调用这些方法,比如一个 int 类型:
s := store.Store{}
s.Set("foo", 42)
通过使用 any,我们失去了 Go 作为静态类型语言的一些好处。相反,我们应该避免使用 any 类型,并尽可能使我们的签名明确。关于我们的示例,这可能意味着为每种类型复制 Get 和 Set 方法:
func (s *Store) GetContract(id string) (Contract, error) {
// ...
}
func (s *Store) SetContract(id string, contract Contract) error {
// ...
}
func (s *Store) GetCustomer(id string) (Customer, error) {
// ...
}
func (s *Store) SetCustomer(id string, customer Customer) error {
// ...
}
在这个版本中,方法具有明确的表达性,降低了理解上的难度。拥有更多的方法并不一定是问题,因为客户端也可以利用接口来创建他们自己的抽象。例如,如果一个客户端只对 Contract 方法感兴趣,它可以编写类似这样的代码:
type ContractStorer interface {
GetContract(id string) (store.Contract, error)
SetContract(id string, contract store.Contract) error
}
any 在哪些情况下有用?让我们看看标准库中的两个例子,其中函数或方法接受 any 类型的参数。第一个例子是在 encoding/json 包中。因为我们可以将任何类型进行序列化(marshal),所以 Marshal 函数接受一个 any 类型的参数:
func Marshal(v any) ([]byte, error) {
// ...
}
另一个例子是在 database/sql 包中。如果查询是参数化的(例如,SELECT * FROM FOO WHERE id = ?),那么参数可以是任何类型。因此,它也使用 any 类型的参数:
func (c *Conn) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
// ...
}
总之,如果确实需要接受或返回任何可能的类型(例如,在进行序列化或格式化时),any 类型可能会很有帮助。通常,我们应该避免以任何代价过度泛化我们编写的代码。或许在某些情况下,稍微重复一些代码可能是更好的选择,如果它能够提高代码的表达力等其他方面的话。