77.Go中interface{}判nil的正确姿势

一:interface{}简介

go中的nil只能赋值给指针、channel、func、interface、map或slice类型的变量

interface 是否根据是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface

  • eface:表示不含 methodinterface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。

  • iface: 表示 non-empty interface 的底层实现。相比于 empty interfacenon-empty 要包含一些 methodmethod 的具体实现存放在 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代表的具体数据。

二、interface{}判空

只有内部类型都为nil,总的interface才是空的。

var inter interface{
   } = nil
if inter==nil{
   
    fmt.Println("empty")
}else{
   
    fmt.Println("not empty")
}

结果为 empty

niluntyped类型,赋值给interface{},则typevalue都是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{},尽管pnil,但是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()
}

相关推荐

  1. 77.Gointerface{}nil正确姿势

    2024-01-29 14:02:02       25 阅读
  2. 【开发掉坑】go interface nil 判断

    2024-01-29 14:02:02       32 阅读
  3. Go 优雅判断 interface 是否为 nil

    2024-01-29 14:02:02       19 阅读
  4. iOS 横竖屏正确打开姿势

    2024-01-29 14:02:02       15 阅读
  5. 你会处理 go nil

    2024-01-29 14:02:02       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-29 14:02:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-29 14:02:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-29 14:02:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-29 14:02:02       18 阅读

热门阅读

  1. 设计一个分布式ID

    2024-01-29 14:02:02       26 阅读
  2. flutter中对底部弹框的应用

    2024-01-29 14:02:02       37 阅读
  3. Flutter 点击空白的地方让软键盘消失

    2024-01-29 14:02:02       34 阅读
  4. vue 组件之间相互传值的6种方法

    2024-01-29 14:02:02       28 阅读
  5. vue.js中如何使用动态组件。

    2024-01-29 14:02:02       34 阅读
  6. 题记(31)--哈夫曼树

    2024-01-29 14:02:02       31 阅读
  7. 怎样开发adobe indesign插件,具体流程?

    2024-01-29 14:02:02       35 阅读
  8. 算法训练|下一个排列

    2024-01-29 14:02:02       38 阅读
  9. 1361:产生数(Produce)

    2024-01-29 14:02:02       30 阅读
  10. 极智芯 | 解读国产CPU之龙芯

    2024-01-29 14:02:02       41 阅读
  11. C语言K&R圣经笔记 6.3结构体数组

    2024-01-29 14:02:02       27 阅读