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

文章目录

  • 一:interface{}简介
  • 二、interface{}判空
  • 三:注意点
  • 四:实际案例

一: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  *itabdata unsafe.Pointer
}type eface struct {_type *_typedata  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 intreflect.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: falsefmt.Printf("intPtr IsNil: %t\n", IsNil(returnIntPtr())) // intPtr IsNil: truefmt.Printf("slice IsNil: %t\n", IsNil(returnSlice())) // slice IsNil: falsefmt.Printf("map IsNil: %t\n", IsNil(returnMap())) // map IsNil: falsefmt.Printf("interface IsNil: %t\n", IsNil(returnInterface())) // interface IsNil: truefmt.Printf("structPtr IsNil: %t\n", IsNil(returnStructPtr()))  // structPtr IsNil: true
}func returnInt() interface{} {var value intreturn value
}func returnIntPtr() interface{} {var value *intreturn value
}func returnSlice() interface{} {var value []stringreturn 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 *Peoplereturn 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()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/652674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1333:【例2-2】Blah数集

【题目描述】 大数学家高斯小时候偶然间发现一种有趣的自然数集合Blah,对于以a为基的集合Ba定义如下: (1)a是集合Ba的基,且a是Ba的第一个元素; (2)如果x在集合Ba中,则2x1和3x1也都在集合Ba中; (3)没有其他元…

9. 嵌入式系统开发:安全性与可靠性设计模式---引言

在复杂的嵌入式系统设计中,为了提高嵌入式系统安全性并保护嵌入式系统免受各种潜在故障的影响,可以采用不同的设计模式。这些模式各自有优势和适用的场景: 1. 受保护的单通道模式(Protected Single Channel Pattern) …

【JavaSE篇】——数组练习

目录 🚩数组转字符串 🚩数组拷贝 🎈拷贝的实质 🎈copyOf方法(new) 🍭copyOf扩容 🎈arraycopy方法(局部的拷贝)(系统类) 🎈copyOfRange方法(局部拷贝) 🎈原数组.clone() &a…

竞赛练一练 第30期:GESP和电子学会相关题目练习

Day14:CIE一级2022.06_报时的公鸡 故事背景:公鸡在黎明时分会打鸣迎接太阳升起,古人也将鸡鸣声当做晨起的“闹钟”。 1. 准备工作 (1)背景:根据下图绘制两张背景; 01 02 (2&…

python 获取未提前定义的属性报错 KeyError

python 获取未提前定义的属性报错 正文 最近用python编写了一个脚本,是对象形式的数据结构。发现python中对于没有定义的属性名,直接用中括号获取会报错。这一点有别于js。简单记录一下啊具体情况如下: obj {"a": 1,"b&qu…

代码随想录算法训练营Day42|0-1背包理论基础、416. 分割等和子集

目录 0-1背包理论基础 0-1背包问题 二维dp数组01背包 算法实现 一维dp数组01背包 ​编辑算法实现 416. 分割等和子集 前言 思路 算法实现 总结 0-1背包理论基础 0-1背包问题 题目链接https://kamacoder.com/problempage.php?pid1046 有n件物品…

【Javaweb程序设计】【C00162】基于SSM的儿童众筹救助系统(论文+PPT)

基于SSM的儿童众筹救助系统(论文PPT) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的儿童众筹救助系统 本系统分为登录模块、前台模块、管理员模块、用户模块以及发起人模块5个功能模块. 登录模块:这个功能模块用来让…

如何让haproxy不命中已经失效的服务器

最近在做负载均衡时,服务总是不稳定,经过日志分析,发现是haproxy负载代理时,命中了已经失效的服务节点。那么如何让haproxy不命中已经失效的服务器呢? 你可以配置服务器的健康检查(Health Check&#xff09…

vs2019报错MSB4019 找不到导入的项目“BuildCustomizations\CUDA 9.2.props”

在VS中执行生成,报错如下:严重性 代码 说明 项目 文件 行 禁止显示状态 错误 MSB4019 找不到导入的项目“D:\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\BuildCustomizations\CUDA 9.2.props”。请确认 Import 声明“D:\Microso…

C++进阶(七)AVL树

📘北尘_:个人主页 🌎个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 文章目录 一、AVL树的概念二、AVL树的旋转1、左单旋2、右单旋3、左右双旋4、右左双旋 三、AVL树的基本实…

jszip和pizzip中文乱码的解决方案

jszip中文乱码的解决方案 jszip官方文档地址:https://stuk.github.io/jszip/ 1、问题分析 在JavaScript中需要用到对Zip压缩包进行操作时,我们往往会使用jszip对压缩包进行编辑。 但是,当我们使用jszip来读取包含中文名的文件时&#xff…

使用ollama本地跑大模型

大家好,我是邵奈一,一个不务正业的程序猿、正儿八经的斜杠青年。 1、世人称我为:被代码耽误的诗人、没天赋的书法家、五音不全的歌手、专业跑龙套演员、不合格的运动员… 2、这几年,我整理了很多IT技术相关的教程给大家,爱生活、爱分享。 3、如果您觉得文章有用,请收藏,…

SpringCloud--FeignGateWay

Feign 创建项目勾选web SpringWeb 1.0 创建生产者SpringCloudFeignProvider 端口号:8081 pom.xml引入依赖 <!--nacos依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery<…

C语言基础:写一个函数,输入一行字符,将此字符串最长的单词输出

方法一&#xff1a; #include<string.h> int find_longest(char line[])//把数组传过来 {int is_alphabetic(char word);int i 0;int length 0;//统计每个字符串的长度int max 0;//比max长就把值赋值给maxint place 0;//最长单词的起始位置int point;//每个字符串第…

JVM-字节码文件的组成

Java虚拟机的组成 Java虚拟机主要分为以下几个组成部分&#xff1a; 类加载子系统&#xff1a;核心组件类加载器&#xff0c;负责将字节码文件中的内容加载到内存中。 运行时数据区&#xff1a;JVM管理的内存&#xff0c;创建出来的对象、类的信息等等内容都会放在这块区域中。…

100.乐理基础-五线谱-是否需要学习五线谱

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;99.乐理基础-简谱的多声部-CSDN博客 简谱与五线谱的区别&#xff0c;各自的优劣势、使用场景、范围等&#xff1a; 要搞懂这个问题&#xff0c;其实核心就是四个词&#xff1a;首调、固定调、单声部、多声部 首调、…

如何在Ubuntu安装配置SVN服务端并实现无公网ip访问内网资料库

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改au…

TypeScript(二) 基础类型

1. typescript的基础类型 数据类型关键字描述任意类型any生命any的变量可以赋值任意类型的值数字类型number整数或分数字符串类型string使用单引号&#xff08;‘’&#xff09;或者双引号&#xff08;“”&#xff09;来表示字符串类型。反引号&#xff08;&#xff09;来定义…

前端 HTML 知识点之 HTML 介绍

1.1 HTML 1.1.1 定义 超文本标记语言&#xff08;英语&#xff1a;HyperTextMarkupLanguage&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言 HTML元素是构建网站的基石 标记语言&#xff08;markup language &#xff09; 由无数个标记&#…

黑马程序员-瑞吉外卖-day5

修改实体类 package com.itheima.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode;i…