您如何在Go的运行时根据其类型创建结构的新实例?


70

在Go中,如何在运行时根据对象的类型创建对象的实例?我想您还需type要先获取对象的实际值吗?

我正在尝试执行惰性实例化以节省内存。

Answers:


82

为了做到这一点,你需要reflect

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // one way is to have a value of the type you want already
    a := 1
    // reflect.New works kind of like the built-in function new
    // We'll get a reflected pointer to a new int value
    intPtr := reflect.New(reflect.TypeOf(a))
    // Just to prove it
    b := intPtr.Elem().Interface().(int)
    // Prints 0
    fmt.Println(b)

    // We can also use reflect.New without having a value of the type
    var nilInt *int
    intType := reflect.TypeOf(nilInt).Elem()
    intPtr2 := reflect.New(intType)
    // Same as above
    c := intPtr2.Elem().Interface().(int)
    // Prints 0 again
    fmt.Println(c)
}

您可以使用结构类型(而不是int)执行相同的操作。还是其他的,真的。只需确保了解map和slice类型时new和make之间的区别即可。


1
在下面针对结构发布了此答案的版本
Peter Berg,

我是否必须基于某个对象或指向该对象的指针实例化它?我正在使用结构。我希望它接受结构本身(而不是其实例化的对象),获取其类型并使用反射实例化它。或者,我想以某种方式使用reflect.New(something)来实例化一个对象,而实际上并没有以通常的方式实例化该结构,因为应该从函数参数将某些东西传递给代码。我该怎么办?
huggie

35

由于reflect.New不会自动在结构字段中使用引用类型,因此可以使用类似以下内容的方法来递归地初始化这些字段类型(在此示例中请注意递归结构定义):

package main

import (
    "fmt"
    "reflect"
)

type Config struct {
    Name string
    Meta struct {
        Desc string
        Properties map[string]string
        Users []string
    }
}

func initializeStruct(t reflect.Type, v reflect.Value) {
  for i := 0; i < v.NumField(); i++ {
    f := v.Field(i)
    ft := t.Field(i)
    switch ft.Type.Kind() {
    case reflect.Map:
      f.Set(reflect.MakeMap(ft.Type))
    case reflect.Slice:
      f.Set(reflect.MakeSlice(ft.Type, 0, 0))
    case reflect.Chan:
      f.Set(reflect.MakeChan(ft.Type, 0))
    case reflect.Struct:
      initializeStruct(ft.Type, f)
    case reflect.Ptr:
      fv := reflect.New(ft.Type.Elem())
      initializeStruct(ft.Type.Elem(), fv.Elem())
      f.Set(fv)
    default:
    }
  }
}

func main() {
    t := reflect.TypeOf(Config{})
    v := reflect.New(t)
    initializeStruct(t, v.Elem())
    c := v.Interface().(*Config)
    c.Meta.Properties["color"] = "red" // map was already made!
    c.Meta.Users = append(c.Meta.Users, "srid") // so was the slice.
    fmt.Println(v.Interface())
}

应该switch ft.Type.Kind()switch ft.Kind()

ft变量的类型为StructField,Kind()本身是reflect.Type的方法。struct
Bilal

1
如果您有一个指向结构的指针怎么办?(&Config{}而不是Config{}
Xeoncross

@Xeoncross在最后一个case字段中进行了介绍,该字段的类型与进行了比较reflect.Ptr
Fuzzybear3965

@ fuzzybear3965谢谢,自我的问题以来,答案已更新。
Xeoncross

19

您可以使用reflect.Zero()它将返回结构类型零值的表示形式。(类似于您这样做var foo StructTypereflect.New()与后者不同,后者将动态分配该结构并为您提供一个指针,类似于new(StructType)


10

这是Evan Shaw给出的一个基本示例,但具有一个结构:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    type Product struct {
        Name  string
        Price string
    }

    var product Product
    productType := reflect.TypeOf(product)       // this type of this variable is reflect.Type
    productPointer := reflect.New(productType)   // this type of this variable is reflect.Value. 
    productValue := productPointer.Elem()        // this type of this variable is reflect.Value.
    productInterface := productValue.Interface() // this type of this variable is interface{}
    product2 := productInterface.(Product)       // this type of this variable is product

    product2.Name = "Toothbrush"
    product2.Price = "2.50"

    fmt.Println(product2.Name)
    fmt.Println(product2.Price)

}

根据newacct的响应,使用Reflect.zero可以是:

   var product Product
   productType := reflect.TypeOf(product)       // this type of this variable is reflect.Type
   productValue := reflect.Zero(productType)    // this type of this variable is reflect.Value
   productInterface := productValue.Interface() // this type of this variable is interface{}
   product2 := productInterface.(Product)       // the type of this variable is Product

这是一篇很棒的文章,介绍了go中的反射基础知识。


2
什么是product2:= productInterface。(产品),何时可以使product2:= Product {}
2846569

问题是没有输入Product我就无法json.Unmarshal到这样的值。因此,对于json包,这似乎毫无意义。
卡米尔·吉兹奇

5

您不需要,reflect并且如果它们共享相同的接口,则可以使用工厂模式轻松实现:

package main

import (
    "fmt"
)

// Interface common for all classes
type MainInterface interface {
    GetId() string
}

// First type of object
type FirstType struct {
    Id string
}

func (ft *FirstType) GetId() string {
    return ft.Id
}

// FirstType factory
func InitializeFirstType(id string) MainInterface {
    return &FirstType{Id: id}
}


// Second type of object
type SecondType struct {
    Id string
}

func (st *SecondType) GetId() string {
    return st.Id
}

// SecondType factory
func InitializeSecondType(id string) MainInterface {
    return &SecondType{Id: id}
}


func main() {
    // Map of strings to factories
    classes := map[string]func(string) MainInterface{
        "first": InitializeFirstType,
        "second": InitializeSecondType,
    }

    // Create a new FirstType object with value of 10 using the factory
    newObject := classes["first"]("10")

    // Show that we have the object correctly created
    fmt.Printf("%v\n", newObject.GetId())


    // Create a new SecondType object with value of 20 using the factory
    newObject2 := classes["second"]("20")

    // Show that we have the object correctly created
    fmt.Printf("%v\n", newObject2.GetId())
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.