# 12 空接口(Empty Interface)

技术上讲，一个空接口(`interface{}`)就是指一个不包含方法的[接口](/essential-go/11-interfaces.md)。

由此得出的结论是，每种类型都符合`interface{}`。

在实践中，一般认为空接口就是Java或C#中的`object`类型的Go语言版，因为它结合了类型和类型的值。

空接口实际上是静态语言中的一种动态类型。

空接口也是在Go程序中实现联合体(`union`)的一种方式。

由于每种类型都适配`interface{}`，所以您可以把任意类型的值赋给`interface{}`类型的变量。这时，您就不能在编译时确定变量的真正类型。

空接口的[零值](/essential-go/02-basic-types/ling-zhi.md)是`nil`。

基本例子：

{% tabs %}
{% tab title="Go" %}

```go
package main

import "fmt"

func printVariableType(v interface{}) {
	switch v.(type) {
	case string:
		fmt.Printf("v is of type 'string'\n")
	case int:
		fmt.Printf("v is of type 'int'\n")
	default:
		// generic fallback
		fmt.Printf("v is of type '%T'\n", v)
	}
}

func main() {
	printVariableType("string") // string
	printVariableType(5)        // int
	printVariableType(int32(5)) // int32
}
```

{% endtab %}

{% tab title="Output" %}

```
v is of type 'string'
v is of type 'int'
v is of type 'int32'
```

{% endtab %}
{% endtabs %}

:point\_right: [点击此处](https://play.golang.org/p/qjlfE7rYTKm) :point\_left: 亲自在编程操场练习上述代码。

在编译时，如果您拿到了接口(包括空接口)类型的数据，那您并不知道它真实的、底层的数据类型。

但您可以在运行时，通过类型断言获取其底层类型。

{% tabs %}
{% tab title="Go" %}

```go
package main

import "fmt"

func printTypeAndValue(iv interface{}) {
	if v, ok := iv.(string); ok {
		fmt.Printf("iv is of type string and has value '%s'\n", v)
		return
	}
	if v, ok := iv.(int); ok {
		fmt.Printf("iv is of type int and has value '%d'\n", v)
		return
	}
	if v, ok := iv.(*int); ok {
		fmt.Printf("iv is of type *int and has value '%p'\n", v)
		return
	}
}

func panicOnInvalidConversion() {
	var iv interface{} = "string"

	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	// pass a string
	printTypeAndValue("string")
	i := 5
	// pass an int
	printTypeAndValue(i)
	// pass a pointer to int i.e. *int
	printTypeAndValue(&i)

	panicOnInvalidConversion()
}
```

{% endtab %}

{% tab title="Output" %}

```
iv is of type string and has value 'string'
iv is of type int and has value '5'
iv is of type *int and has value '0xc00002c008'
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion()
	/tmp/sandbox390319121/prog.go:23 +0x45
main.main()
	/tmp/sandbox390319121/prog.go:36 +0x9a
Program exited: status 2.
```

{% endtab %}
{% endtabs %}

## 类型断言(Type assertion)

类型断言使得您能够检查空接口的值是否是某个给定的类型。

为了语法的完整性，类型交换机(type switch)有简短版本：`v := iv.(int)`（或者`v, ok := iv.(int)`）。这又称为类型断言。

跟完整版的类型交换机不同的是，如果您所断言的类型与变量的实际类型不符时，简短版将会发生恐慌(panic)。

{% tabs %}
{% tab title="Go" %}

```go
func panicOnInvalidConversion(iv interface{}) {
	v := iv.(int)
	fmt.Printf("v is int of value: %d\n", v)
}

func main() {
	panicOnInvalidConversion("string")
}
```

{% endtab %}

{% tab title="Output" %}

```
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.panicOnInvalidConversion(0x4a0b40, 0x4dae60)
	/tmp/sandbox387618651/prog.go:6 +0xd6
main.main()
	/tmp/sandbox387618651/prog.go:11 +0x39

Program exited: status 2.
```

{% endtab %}
{% endtabs %}

根据经验，您不应该尝试去发现接口类型的底层值，因为这样做刺破了抽象层。

## 类型交换机(type switch)

{% hint style="info" %}

#### 译者注

关于Switch本词的译法探讨，我们先看看英文语境中有哪些东西叫switch。

例如电路开关：控制电流应通向哪条线路(断路也是线路之一)；轨道转辙器：控制铁轨岔口向哪个方向联通；网络交换机：控制输入的数据报文应该由哪个端口输出……

可见，**Switch的原生语义逻辑是指，一个系统中流动的对象(如电路中的电流、铁轨上的火车、互联网上的数据报文)，当流经“switch”时，“switch”负责把它导往合适的去向**。

而中英词典中对switch的中文译法只有：开关、转换、调换、交换机、(轨道)转辙器。

此处，感觉只有“类型交换机”更贴切，毕竟大家都熟知网络交换机的作用，应当不会理解为“换位置、做交易”之类的交换。而“类型开关、类型转换、类型调换”相去甚远了。
{% endhint %}

switch语句可以根据被接口包装的值得实际类型进行派发。

{% tabs %}
{% tab title="Go" %}

```go
package main

import (
	"fmt"
	"strconv"
)

func smartConvertToInt(iv interface{}) (int, error) {
	// inside case statements, v is of type matching case type
	switch v := iv.(type) {
	case int:
		return v, nil
	case string:
		return strconv.Atoi(v)
	case float64:
		return int(v), nil
	default:
		return 0, fmt.Errorf("unsupported type: %T", iv)
	}
}

func printSmartConvertToInt(iv interface{}) {
	i, err := smartConvertToInt(iv)
	if err != nil {
		fmt.Printf("Failed to convert %#v to int\n", iv)
		return
	}
	fmt.Printf("%#v of type %T converted to %d\n", iv, iv, i)
}

func main() {
	printSmartConvertToInt("5")
	printSmartConvertToInt(4)
	printSmartConvertToInt(int32(8))
	printSmartConvertToInt("not valid int")
}
```

{% endtab %}

{% tab title="Output" %}

```
"5" of type string converted to 5
4 of type int converted to 4
Failed to convert 8 to int
Failed to convert "not valid int" to int

Program exited.
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://denglj.gitbook.io/essential-go/12-empty-interface.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
