# 12 空接口(Empty Interface)

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

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

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

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

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

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

空接口的[零值](https://denglj.gitbook.io/essential-go/02-basic-types/ling-zhi)是`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 %}
