通过反射在Go中快速检测空值的方法

huangapple go评论85阅读模式
英文:

Quick way to detect empty values via reflection in Go

问题

我在一个interface{}中存储了一个int/string/bool/等等的值,并且想要确定它是否未初始化,也就是说它的值是以下之一:

  • 0
  • ""
  • false
  • 或者 nil

我该如何检查这个呢?

英文:

I have a int/string/bool/etc.. value stored in an interface{} and want to determine if it's uninitialized, meaning that it has a value of either

  • 0
  • ""
  • false
  • or nil

How do I check this?

答案1

得分: 77

从我理解的来看,你想要的是这样的:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}

当谈到接口和nil时,人们常常会混淆两个非常不同且无关的概念:

  1. 一个nil接口值,它是一个没有底层值的接口值。这是接口类型的零值。
  2. 一个非nil的接口值(即它有一个底层值),但它的底层值是其底层类型的零值。例如,底层值是nil映射、nil指针或0数字等。

我理解你在问第二个问题。


更新:由于上面的代码使用了==,它对于不可比较的类型是不起作用的。我相信使用reflect.DeepEqual()代替将使其适用于所有类型:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}
英文:

From what I understand, you want something like:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}

When talking about interfaces and nil, people always get confused with two very different and unrelated things:

  1. A nil interface value, which is an interface value that doesn't have an underlying value. This is the zero value of an interface type.
  2. A non-nil interface value (i.e. it has an underlying value), but its underlying value is the zero value of its underlying type. e.g. the underlying value is a nil map, nil pointer, or 0 number, etc.

It is my understanding that you are asking about the second thing.


Update: Due to the above code using ==, it won't work for types that are not comparable. I believe that using reflect.DeepEqual() instead will make it work for all types:

func IsZeroOfUnderlyingType(x interface{}) bool {
    return reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface())
}

答案2

得分: 23

Go 1.13 (Q3 2019)应该简化了该检测过程:

新的Value.IsZero()方法报告一个Value是否是其类型的零值。

它是在src/reflect/value.go中实现的,来自提交c40bffdCL 171337,解决了issue 7501

参见playground示例(一旦支持Go 1.13)。

var p *string
v := reflect.ValueOf(p)

fmt.Printf("1.13 v.IsZero()='%v' vs. IsZero(v)='%v'\n", v.IsZero(), IsZero(v))

// 输出:
// 1.13 v.IsZero()='true' vs. IsZero(v)='true'

然而:

v = v.Elem()
fmt.Printf("1.13 v.Elem().IsZero()='%v' vs. IsZero(v.Elem())='%v'\n", v.IsZero(), IsZero(v))

// 报错:
//
// panic: reflect: call of reflect.Value.IsZero on zero Value

issue 46320中所解释的:

你描述的问题是由于Go中接口类型的性质以及reflect包是建立在接口类型之上的事实所导致的。

接口类型的值总是描述了其他值。
当你将一个接口类型的值传递给reflect.ValueOf时,你得到的是描述那个其他值的反射对象,而不是接口类型的值。

要使用reflect包处理interface值,通常需要使用指针。

这与IsZero()无关,这只是reflect包的工作原理。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var c interface{}

	fmt.Println(reflect.ValueOf(&c).Elem().Kind())
	fmt.Println(reflect.ValueOf(&c).Elem().IsZero())
}

基本上,在调用IsZero()之前需要调用IsValid()

英文:

Go 1.13 (Q3 2019) should simplify that detection process:

> The new Value.IsZero() method reports whether a Value is the zero value for its type.

It is implemented in src/reflect/value.go, from commit c40bffd and CL 171337, resolving issue 7501

See playground example (as soon as Go 1.13 is supported)

var p *string
v := reflect.ValueOf(p)

fmt.Printf("1.13 v.IsZero()='%v' vs. IsZero(v)='%v'\n", v.IsZero(), IsZero(v))

// Ouput:
// 1.13 v.IsZero()='true' vs. IsZero(v)='true'

However:

v = v.Elem()
fmt.Printf("1.13 v.Elem().IsZero()='%v' vs. IsZero(v.Elem())='%v'\n", v.IsZero(), IsZero(v))

// Panic:
//
// panic: reflect: call of reflect.Value.IsZero on zero Value

As explained in issue 46320:

> The problem you are describing is due to the nature of interface types in Go, and to the fact that the reflect package is built on interface types.
>
> A value of interface type always describes some other value.
When you pass a value of interface type to reflect.ValueOf, you get a reflection object describing that other value, not the value of interface type.
>
> To work with interface values with the reflect package you generally need to use pointers.
>
> This doesn't have anything to do with IsZero(), it's just how the reflect package works.
>
> go
> package main
>
> import (
> "fmt"
> "reflect"
> )
>
> func main() {
> var c interface{}
>
> fmt.Println(reflect.ValueOf(&c).Elem().Kind())
> fmt.Println(reflect.ValueOf(&c).Elem().IsZero())
> }
>

>
> Basically, one needs to call IsValid() before IsZero()

答案3

得分: 4

interface{}类型的零值只能是nil,不能是0""false

package main

import "fmt"

func main() {
        var v interface{}
        fmt.Println(v == nil, v == 0, v == "", v == false)
}

(也可以在 http://play.golang.org/p/z1KbX1fOgB 查看)


输出结果

true false false false

*: [Q]当为一个值分配内存时,无论是通过声明还是通过make或new的调用,如果没有提供显式的初始化,该内存将被赋予默认初始化。这个值的每个元素都被设置为其类型的零值:布尔类型为false,整数类型为0,浮点数类型为0.0,字符串类型为"",指针、函数、接口、切片、通道和映射类型为nil。[/Q]

英文:

The zero value* of type interface{} is only nil, not 0 or "" or false.

package main

import "fmt"

func main() {
        var v interface{}
        fmt.Println(v == nil, v == 0, v == "", v == false)
}

(Also http://play.golang.org/p/z1KbX1fOgB)


Output

true false false false

*: [Q]When memory is allocated to store a value, either through a declaration or a call of make or new, and no explicit initialization is provided, the memory is given a default initialization. Each element of such a value is set to the zero value for its type: false for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps.[/Q]

答案4

得分: 0

@newacct的答案无法检测原始零值,调用reflect.Value.Interface()会导致错误。可以使用reflect.Value.IsValid()来检查。

更新方法:

func IsZero(v reflect.Value) bool {
    return !v.IsValid() || reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}

func TestIsZero(t *testing.T) {
    var p *string
    v := reflect.ValueOf(p)

    assert.Equal(t, true, v.IsValid())
    assert.True(t, IsZero(v))

    assert.Equal(t, uintptr(0), v.Pointer())

    v = v.Elem()
    assert.Equal(t, false, v.IsValid())
    assert.True(t, IsZero(v))
}
英文:

@newacct's answer can't detect raw zero value, calling reflect.Value.Interface() on which will cause error. It can use reflect.Value.IsValid() to check that.

// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
// Most functions and methods never return an invalid value.
// If one does, its documentation states the conditions explicitly.
func (v Value) IsValid() bool 

Update the methods:

func IsZero(v reflect.Value) bool {
	return !v.IsValid() || reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}

func TestIsZero(t *testing.T) {
	var p *string
	v := reflect.ValueOf(p)

	assert.Equal(t, true, v.IsValid())
	assert.True(t, IsZero(v))

	assert.Equal(t, uintptr(0), v.Pointer())

	v = v.Elem()
	assert.Equal(t, false, v.IsValid())
	assert.True(t, IsZero(v))
}

huangapple
  • 本文由 发表于 2012年12月16日 21:37:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/13901819.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定