在go
中的nil
只能赋值给指针、channel、func、interface、map或slice类型
的变量
interface
是否根据是否包含有 method
,底层实现上用两种 struct
来表示:iface 和 eface
。
eface
:表示不含 method
的 interface
结构,或者叫 empty interface
。对于 Golang
中的大部分数据类型都可以抽象出来 _type
结构,同时针对不同的类型还会有一些其他信息。
iface
: 表示 non-empty interface
的底层实现。相比于 empty interface
,non-empty
要包含一些 method
。method
的具体实现存放在 itab.fun
变量里。
定义在 src/runtime/runtime2.go
中
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
上述就是两种 interface
的定义。然后我们再看 iface
中的 itab
结构:(被定义在 src/runtime/runtime2.go
中)
type itab struct {
inter *interfacetype // 接口的类型
_type *_type // 实际对象类型
// ... 还有一些其他字段
}
type _type struct {
size uintptr // 大小信息
.......
hash uint32 // 类型信息
tflag tflag
align uint8 // 对齐信息
.......
}
不管是iface
还是eface
,我们可以明确的是interface
包含有一个字段_type *_type
表示类型,有一个字段data unsafe.Pointer
指向了这个interface代表的具体数据。
只有内部类型都为nil
,总的interface
才是空的。
var inter interface{} = nil
if inter==nil{
fmt.Println("empty")
}else{
fmt.Println("not empty")
}
结果为 empty
nil
为untyped
类型,赋值给interface{}
,则type
和value
都是nil
,比较的结果是true
其他有类型的赋值给interface{},结果是
false`,例如
var inter interface{} = (*int)(nil)
if inter==nil{
fmt.Println("empty")
}else{
fmt.Println("not empty")
}
结果为 not empty
对interface{}
判空的方法是使用反射的方式进行判断
var inter interface{} = (*int)(nil)
if IsNil(inter){
fmt.Println("empty")
}else{
fmt.Println("not empty")
}
func IsNil(i interface{}) bool {
vi := reflect.ValueOf(i)
if vi.Kind() == reflect.Ptr {
return vi.IsNil()
}
return false
}
结果为 empty
nil
,也不意味着接口本身为 nil
。panic
的: var x int
reflect.ValueOf(x).IsNil()
而输出也是非常明显的指出错误:
panic: reflect: call of reflect.Value.IsNil on int Value
因为不可赋值 nil
的 interface
是不能使用 reflect.Value.IsNil
方法的。
那么问题就很好解决了。
解决方式
我们在执行reflect.Value.IsNil
方法之前,进行一次判断是否为指针即可:
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}
重点在于rv.Kind() == reflect.Ptr && rv.IsNil()
这段代码。
这段代码的作用:
x
的类型是否为指针。x
的值是否真的为 nil
。下面我们使用几种常见的数据类型来进行测试:
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}
func main() {
fmt.Printf("int IsNil: %t\n", IsNil(returnInt())) // int IsNil: false
fmt.Printf("intPtr IsNil: %t\n", IsNil(returnIntPtr())) // intPtr IsNil: true
fmt.Printf("slice IsNil: %t\n", IsNil(returnSlice())) // slice IsNil: false
fmt.Printf("map IsNil: %t\n", IsNil(returnMap())) // map IsNil: false
fmt.Printf("interface IsNil: %t\n", IsNil(returnInterface())) // interface IsNil: true
fmt.Printf("structPtr IsNil: %t\n", IsNil(returnStructPtr())) // structPtr IsNil: true
}
func returnInt() interface{} {
var value int
return value
}
func returnIntPtr() interface{} {
var value *int
return value
}
func returnSlice() interface{} {
var value []string
return value
}
func returnMap() interface{} {
var value map[string]struct{}
return value
}
func returnInterface() interface{} {
var value interface{}
return value
}
type People struct {
Name string
}
func returnStructPtr() interface{} {
var value *People
return value
}
我们先后使用了 int、*int、slice、map、interface{}、自定义结构体
来测试此IsNil
方法。运行程序输出为:
int IsNil: false
intPtr IsNil: true
slice IsNil: false
map IsNil: false
interface IsNil: true
structPtr IsNil: true
从测试结果来看,目前是符合我们对此方法的定位的。
工作中实际的场景,一般是因为面向接口编程,可能经过了很长的链路处理,在某个节点返回了一个空的结构体,如下
某个环节有一个A
方法可能会返回一个nil
的*People
type People struct {
Name string
}
func A() *People {
// ... 一些逻辑
return nil
}
在调用方可能是用interface{}
接收的,然后判断是否为空,用的==
,发现不为nil
,后续使用该变量就会报空指针异常了
p := A()
func B(people interface{}){
if people != nil {
fmt.Println(people.Name)
}
}
如上代码便会抛panic
,因为p
赋值给了interface{}
,尽管p
是nil
,但是people
并不是nil
,因为_type
不是空,但是使用people.Name
会报空指针异常panic
,因为data
是空的。正确的做法应该是如下形式
p := A()
func B(people interface{}){
if !IsNil(people) {
fmt.Println(people.Name)
}
}
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}