Golang中如何将interface{}转换为指定的接口类型?
Golang中如何将interface{}转换为指定的接口类型? 主题始于:https://github.com/golang/go/issues/32432
我已将问题简化为以下代码:
package main
type Incrementor interface {
Increment()
}
type I int
func (i *I) Increment() {
*i++
}
func main() {
var i I
var j interface{} = i
j.(Incrementor).Increment() // panic: interface conversion: main.I is not main.Incrementor: missing method Increment
}
我需要在变量 j 上调用 Increment() 方法吗?有没有办法实现这个操作?
更多关于Golang中如何将interface{}转换为指定的接口类型?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
var j interface{} = &i
更多关于Golang中如何将interface{}转换为指定的接口类型?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
太棒了!这正是我问题的解决方案!我曾考虑过类似的方法,但没想到实现起来如此简单。
// 递归实现部分已省略 // ValidateValue 深入值元素并递归调用自身
那么这部分具体是在做什么呢?
关键在于这类项目并不要求必须实现接口,例如当值未实现接口时,验证过程会直接跳过。以包含结构体的切片为例:当我们递归验证结构体时,切片本身并不需要实现接口。
顺便提一下,https://github.com/dealancer/validate/blob/v2/validate.go#L188 处的代码看起来没问题。
也许有人能想出办法,但我认为你无法两者兼得:要么在函数中要求验证器,这样能够静态检查指针接收器,但失去了跳过根值验证的能力(或在文档中建议用户编写虚拟实现);要么允许跳过未实现接口的值,但必须接受值和指针类型的方法集不同,并且也会跳过它们。
我仍然不太理解你想要实现什么。
是的,获取接口的地址并将其放入另一个接口中不太可能有帮助。这不是我的建议。在你最初的代码中,你希望在将具体类型放入接口之前获取其地址,这样它就能同时实现相关的方法。
但你现在已经把代码示例改成了其他内容。这个新代码缺乏足够的上下文来回答你的问题。CustomValidators 是定义在哪些类型上的?你能提供一个展示问题的自包含示例吗?你认为应该发生什么,但实际上没有发生?
将类型转换为接口的语法类似,但工作方式不同。为什么?
package main
type Incrementor interface {
Increment()
}
type I int
func (i *I) Increment() {
*i++
}
func main() {
var i I
// 正常工作
var j interface{} = i
jUnboxed := j.(I) // 这是可以的
jUnboxed.Increment()
// 无法工作
var k interface{} = i
kUnboxed := k.(Incrementor) // panic: interface conversion: main.I is not main.Incrementor: missing method Increment
kUnboxed.Increment()
}
你的做法实际上是正确的,但问题出在其他地方。你在 *I 上声明了 Increment 方法,而不是在 I 上,这意味着该方法只能通过指针接收器调用。你试图将 I 断言为 Incrementor,而实际上应该将 *I 断言为 Incrementor,如下所示:
package main
type Incrementor interface {
Increment()
}
type I int
func (i *I) Increment() {
*i++
}
func main() {
i := new(I)
var j interface{} = i
j.(Incrementor).Increment()
}
当然,如果你要将类型 T 转换为 Validator,那么 T 上必须定义 Validate 函数。在 *T 上定义的函数是不行的,就像在其他类型 U 上定义的 Validate 函数也不行一样。
或许你可以这样做:
func ValidateValue(value reflect.Value) error {
if validator, ok := value.Interface().(Validator); ok { …; return }
t := reflect.New(value.Type()) // 类型为 *T
t.Elem().Set(value) // 将值复制到 *T
if validator, ok := t.Interface().(Validator); ok { …; return }
}
需要注意的是,这会复制底层对象,因此 Validate 指针方法所做的任何修改都会被静默忽略。
嗯,我理解您的意思。我认为在Go语言中,接口应该定义接收器类型,因为例如我不希望Validate在调用时改变结构体。如果用户通过指针接收器定义Validate,我有两种选择:
- 为用户提供文档说明不要指定指针接收器。如果用户没有阅读文档,很容易犯错
- 仍然通过反射调用,但如果用户期望任何修改,他将无法获得修改
这两种情况都不理想!它们可以通过语言层面的改变来消除!这样接口就可以拥有接收器类型。
type I interface {
A() // 值接收器
*B() // 指针接收器
}
我认为,你只是把错误地传递值而非指针并静默跳过Validate调用的问题,转移成了静默调用Validate却在传递值时无法完成任何修改的问题——即使用户只在指针接收器上定义了Validate方法并可能在其中进行修改。我的意思是,如果我传递一个值,理应预期它不会被修改;但如果没有在值类型上定义Validate方法,那么我真正应该预期的是该方法不会执行。无论如何,你仍然需要依赖文档说明,尤其是现在你加入了一个反直觉的备用方案,仅仅因为你猜测我犯了错误(万一我本就不希望你验证我的值,而只想验证其内部状态,所以才这样传递值而非指针呢?)
当然,这是你的代码库,你可以按喜好定义其语义。我只是认为,对于未实现Validate方法的对象,跳过验证调用才应该是既定的契约。
引用Johan链接文章中的内容:
现在要将Camel传递给LongWalk,需要传入指向Camel的指针:
c := &Camel{"Bill"} LongWalk(c) 或 c := Camel{"Bill"} LongWalk(&c)注意:虽然你仍然可以直接在Camel上调用Walk方法:
c := Camel{"Bill"} c.Walk(500) // 这样是可行的能够这样做的原因是Go编译器会自动将这行代码转换为(&c).Walk(500)。但是,将值传入接口时这种机制就不起作用了。原因是接口中的值位于隐藏的内存位置,因此编译器无法自动为你获取该内存的指针(用Go的术语来说,这被称为"不可寻址")。
总结来说,如果指针类型实现了接口,存在语法糖允许你从非指针值调用方法,编译器会为你获取指针并进行调用。所以在第一种情况下,你正在进行转换然后调用方法,这会触发上述机制。在第二种情况下,你试图将类型为I的变量转换为Incrementor接口,但I并没有真正实现Incrementor接口,因此如预期那样会出现错误。
我不太确定是否有好的方法来实现这一点,毕竟你是在运行时检查传入的值是否实现了接口。这与用户传入任何未实现Validator接口的值类型没有本质区别,就像这个例子中展示的那样,因此你可以返回一个错误说明:
// 第三方包提供的Validator接口
type Validator interface {
Validate() error
}
func ValidateValue(value reflect.Value) error {
if validator, ok := value.Interface().(Validator); ok {
if err := validator.Validate(); err != nil {
return err
}
} else {
return errors.New("given value doesn't implement the Validator interface")
}
return nil
}
// S结构体由用户定义
type S struct {
field int
}
// T未实现Validator
type T struct {
field int
}
func (s *S) Validate() error {
if s.field <= 0 {
return errors.New("Field shoud be positive")
}
return nil
}
// 用户程序
func main() {
t := T{field: 0}
//T未实现validator,因此会得到错误。
err := ValidateValue(reflect.ValueOf(t))
fmt.Println(err)
s := S{field: 0}
//S也未实现validator,因此也会得到错误。
err = ValidateValue(reflect.ValueOf(s))
fmt.Println(err)
}
在Go语言中,将interface{}转换为特定接口类型时,需要注意值接收器和指针接收器的区别。在你的代码中,I类型实现了Incrementor接口,但Increment()方法使用的是指针接收器(i *I)。
问题在于:
- 变量
i是I类型(值类型) - 当赋值给
interface{}类型的j时,存储的是值拷贝 - 值类型无法满足指针接收器方法的要求
以下是两种解决方案:
方案1:使用指针类型
package main
type Incrementor interface {
Increment()
}
type I int
func (i *I) Increment() {
*i++
}
func main() {
var i I
// 使用指针赋值给interface{}
var j interface{} = &i
j.(Incrementor).Increment() // 正常工作
}
方案2:修改方法为值接收器
package main
type Incrementor interface {
Increment()
}
type I int
func (i I) Increment() {
// 注意:这里修改的是副本,不会影响原始值
i++
}
func main() {
var i I
var j interface{} = i
j.(Incrementor).Increment() // 正常工作,但不会修改原始i的值
}
方案3:安全的类型断言
package main
import "fmt"
type Incrementor interface {
Increment()
}
type I int
func (i *I) Increment() {
*i++
}
func main() {
var i I
var j interface{} = &i
if inc, ok := j.(Incrementor); ok {
inc.Increment()
fmt.Println("Increment called successfully")
} else {
fmt.Println("Type assertion failed")
}
}
推荐使用方案1,因为它保持了方法的原始语义(能够修改原始值),并且通过安全的类型断言来避免panic。


