предисловие
interface
(т.е. интерфейс), является важной концепцией и точкой знаний в языке Go, а также мощнымreflect
это основано наinterface
. Эта статья представляет собой введение в язык Go.interface
иreflect
Более полный обзор релевантных знаний можно рассматривать как подведение итогов моего обучения на данном этапе, чтобы проанализировать прошлое и узнать что-то новое. Статья длинная, будьте готовы к читателю.
интерфейс
определение
В Go, если пользовательский тип (например,struct
) реализуетinterface
Все методы в , то можно сказать, что этот тип реализует этот интерфейс. Интерфейс можно определить следующим образом:
type 接口名称 interface {
method1(参数列表) 返回值列表
method1(参数列表) 返回值列表
...
}
interface
представляет собой набор методов, которые не требуется реализовывать, иinterface
серединанет переменной.interface
Коллекция методов может представлять характеристики и возможности объекта. Когда пользовательские типы должны использовать эти методы, эти методы могут быть реализованы по мере необходимости. Возьмите каштан:
package main
import (
"fmt"
)
type Animal interface {
Eat()
Run()
}
type Dog struct {
Name string
}
type Cat struct {
Name string
}
func (dog *Dog) Eat() {
fmt.Printf("%s is eating.", dog.Name)
}
func (dog *Dog) Run() {
fmt.Printf("%s is running.", dog.Name)
}
func (cat *Cat) Eat() {
fmt.Printf("%s is eating.", cat.Name)
}
func (cat *Cat) Run() {
fmt.Printf("%s is running.", cat.Name)
}
func main() {
var animal1 Animal
animal1 = &Dog{"doggy"}
animal1.Eat()
animal1.Run()
var animal2 Animal
animal2 = &Cat{"catty"}
animal2.Eat()
animal2.Run()
}
Вышеприведенное определяет интерфейс Animal, а также тип Dog и тип Cat. Оба типа Dog и Cat реализуют методы интерфейса Animal, поэтому Dog и Cat являются типами Animal.
Одновременный интерфейсне может создать экземпляр сам по себе, но, как видно из приведенного выше примера, переменная типа интерфейса может указывать на экземпляр пользовательского типа, реализующего интерфейс.interface
Тип по умолчанию является указателем (ссылочным типом), если нетinterface
Используйте его для инициализации, тогда он выведетnil
.
пустой интерфейс
пустой интерфейсinterface{}
Методов нет, поэтому все типы реализуют пустой интерфейс, т.е. мы можем присвоить любую переменную пустому интерфейсу. Измените основную функцию выше:
func main() {
var animal interface{}
dog := &Dog{"doggy"}
animal = dog
fmt.Println(animal)
}
результат операции:
&{doggy}
наследование интерфейса
Интерфейс может наследовать от нескольких других интерфейсов.Если вы хотите реализовать этот интерфейс, вы должны реализовать методы во всех унаследованных интерфейсах.
package main
import (
"fmt"
)
type Eater interface {
Eat()
}
type Runner interface {
Run()
}
type Animal interface {
Eater
Runner
}
// 这里定义一个Dog的struct,并实现eat方法和run方法,这样就实现了动物的接口
type Dog struct {
Name string
}
func (dog *Dog) Eat() {
fmt.Printf("%s is eating.", dog.Name)
}
func (dog *Dog) Run() {
fmt.Printf("%s is running.", dog.Name)
}
func main() {
var animal1 Animal
animal1 = &Dog{"doggy"}
animal1.Eat()
animal1.Run()
}
утверждение типа
Когда мы не уверены, какой тип переменной хранится в переменной интерфейса, мы можем использовать утверждение типа для определения типа переменной.
var animal1 Animal
animal1 = &Dog{"doggy"}
dog := animal1.(*Dog)
При утверждении типа, если типы не совпадают, он сообщитpanic
, так что нужно добавить механизм обнаружения, если он успешен, то все в порядке, иначе не сообщатьpanic
.
var animal1 Animal
animal1 = &Dog{"doggy"}
if dog, ok := animal1.(*Dog); ok {
fmt.Println("convert success")
dog.Run()
} else {
fmt.Println("convert fail")
}
Кроме того, мы также можем использоватьswitch-type
Синтаксис утверждения типа:
package main
import (
"fmt"
)
type Eater interface {
Eat()
}
type Runner interface {
Run()
}
type Animal interface {
Eater
Runner
}
type Dog struct {
Name string
}
type Cat struct {
Name string
}
func (dog *Dog) Eat() {
fmt.Printf("%s is eating.", dog.Name)
}
func (dog *Dog) Run() {
fmt.Printf("%s is running.", dog.Name)
}
func (cat *Cat) Eat() {
fmt.Printf("%s is eating.", cat.Name)
}
func (cat *Cat) Run() {
fmt.Printf("%s is running.", cat.Name)
}
func TypeJudge(animals ...interface{}) {
for index, animal := range animals {
switch animal.(type) {
case *Dog:
fmt.Printf("第%d个参数是Dog类型\n", index)
case *Cat:
fmt.Printf("第%d个参数是Cat类型\n", index)
default:
fmt.Println("不确定类型")
}
}
}
func main() {
var animal1 Animal
animal1 = &Dog{"doggy"}
var animal2 Animal
animal2 = &Cat{"catty"}
TypeJudge(animal1, animal2)
}
эффект
interface
Значение языка Go заключается в том, что он реализует дженерики.Например, функция должна иметь возможность получать параметры разных типов или возвращать значения разных типов, вместо указания типа параметров или возвращаемых значений в начало, чтобы функция могла поддерживать все типы:
func FuncName(arg1 interface{}, rest ...interface{}) interface{} {
// ...
}
Объектно-ориентированные языки, такие как C++ и Java, обладают характеристиками полиморфизма.Можно сказать, чтоinterface
Это форма реализации полиморфизма в Go. Один и тот же интерфейс может быть реализован разными классами (настраиваемыми типами), так что функции с одним и тем же именем могут вызываться, но выполнять совершенно разные функции.
Иногда мы можем использоватьinterface
Реализовать очень умные функции: обычно мы определяем слайс (slice) будем указывать определенный тип, но иногда нам нужно, чтобы элементы в слайсе были переменными любого типа, на этот разinterface
Это пригодилось. Ниже приведено использование при обновлении данных в таблице базы данных в коде переходаinterface
Реализованная операция Сао, читатели могут испытатьinterface
Удобство принесло:
func generateSQLForUpdatingArticle(article model.ArticleStruct) (string, []interface{}) {
var columns = make([]string, 0)
var arguments = make([]interface{}, 0)
if len(article.CommentCount) > 0 {
columns = append(columns, "comment_count = ?")
arguments = append(arguments, article.CommentCount)
}
if len(article.Source) > 0 {
columns = append(columns, "source = ?")
arguments = append(arguments, article.Source)
}
if len(article.Summary) > 0 {
columns = append(columns, "summary = ?")
arguments = append(arguments, article.Summary)
}
if len(article.Content) > 0 {
columns = append(columns, "content = ?")
arguments = append(arguments, article.Content)
}
sql := fmt.Sprintf("UPDATE article_structs SET %s WHERE sid = %s", strings.Join(columns, ","), article.Sid)
return sql, arguments
}
func UpdateArticle(article model.ArticleStruct) error {
sql, arguments := generateSQLForUpdatingArticle(article)
if err := db.Exec(sql, arguments...).Error; err != nil {
log.Println("Updating article failed with error:", err)
return err
}
return nil
}
Однако пустой интерфейсinterface{}
Хотя он может содержать любое значение, он также создает проблему: пустой интерфейс будет скрывать соответствующее представление значения и все общедоступные методы, поэтому только мы знаем конкретный динамический тип, чтобы использовать утверждение типа для доступа к внутреннему значению. не имеет ничего особенного в отношении внутренних значений; если мы заранее не знаем конкретный тип значения, на которое указывает пустой интерфейс, мы можем оказаться в замешательстве.
В настоящее время, если мы хотим знать, что представляет собой переменная типа интерфейса (какого типа) и какими возможностями она обладает (какие методы существуют), нам нужно «зеркало», которое может отражать (reflect
) из конкретного содержимого этой переменной. В языке Go тоже есть такой инструмент —reflect
.
отражать (отражение)
концепция
В компьютерных науках рефлексия относится к классу приложений, которые описывают себя и контролируют себя. То есть такие приложения реализуют описание своего поведения с помощью некоторого механизма (
self-representation
) и мониторинг (examination
), и может корректировать или изменять состояние и связанную с ним семантику поведения, описываемого приложением, в соответствии с состоянием и результатом его собственного поведения.
Языки, поддерживающие отражение, могут интегрировать информацию об отражении переменных, такую как имена полей, информацию о типе, информацию о структуре и т. д., в исполняемый файл во время компиляции программы и предоставлять программе интерфейс для доступа к информации об отражении, поэтому что ее можно получить во время выполнения программы Типы отражения информации и возможность их модификации.
Прежде чем говорить об отражении, нам нужно понять некоторые принципы дизайна шрифтов в Golang:
Переменная состоит из двух частей: type (тип) и value (значение).
тип делится на
static type
иconcrete type
. вstatic type
тип данных, который мы используем на этапе кодирования, например, int, string, bool и т. д.; иconcrete type
являетсяruntime
Видимый системой тип.
Может ли переменная типа интерфейса быть успешной в утверждении типа, зависит от
concrete type
вместоstatic type
.
Тип переменной, задающей тип в языке Go, является статическим, т.е.static type
, который определяется при создании переменной, а отражение в основном должно сотрудничатьinterface
Используются переменные типа, типы этих переменныхconcrete type
.
В реализации Go каждыйinterface
Переменные типа имеют соответствующийpair
, pair
Фактическая переменная записывается вvalue
иtype
:
(value, type)
interface
Переменная типа содержит два указателя, которые указывают на фактическое значение переменной (значение) и тип (соответствующийconcrete type
).interface
иpair
Существование является необходимым условием для реализации отражения в Golang, а отражение — это механизм, используемый для определения значения и типа, хранящихся внутри переменной типа интерфейса. Кстати говоря, это, естественно, приведет кreflect
Два класса данных в пакетеType
иValue
.
Reflect.Type и Reflect.Value
reflect.Type
reflect
в сумкеType
Интерфейс определяется следующим образом:
type Type interface {
// Kind返回该接口的具体分类
Kind() Kind
// Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
Name() string
// PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
// 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
PkgPath() string
// 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
// 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
String() string
// 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
Size() uintptr
// 返回当从内存中申请一个该类型值时,会对齐的字节数
Align() int
// 返回当该类型作为结构体的字段时,会对齐的字节数
FieldAlign() int
// 如果该类型实现了u代表的接口,会返回真
Implements(u Type) bool
// 如果该类型的值可以直接赋值给u代表的类型,返回真
AssignableTo(u Type) bool
// 如该类型的值可以转换为u代表的类型,返回真
ConvertibleTo(u Type) bool
// 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
Bits() int
// 返回array类型的长度,如非数组类型将panic
Len() int
// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
Elem() Type
// 返回map类型的键的类型。如非映射类型将panic
Key() Type
// 返回一个channel类型的方向,如非通道类型将会panic
ChanDir() ChanDir
// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
NumField() int
// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
Field(i int) StructField
// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
FieldByIndex(index []int) StructField
// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
FieldByName(name string) (StructField, bool)
// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
// 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
// 如非函数类型将panic
IsVariadic() bool
// 返回func类型的参数个数,如果不是函数,将会panic
NumIn() int
// 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
In(i int) Type
// 返回func类型的返回值个数,如果不是函数,将会panic
NumOut() int
// 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
Out(i int) Type
// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
NumMethod() int
// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
Method(int) Method
// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
MethodByName(string) (Method, bool)
// 内含隐藏或非导出方法
}
мы можем пройтиreflect.TypeOf
принять любойinterface{}
введите и верните соответствующий динамический типreflect.Type
:
num := reflect.TypeOf(1)
fmt.Println(num.String())
fmt.Println(num)
посмотриTypeOf()
Код реализации:
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
Его можно найтиTypeOf
Тип параметра функцииinterface{}
, а конкретное значение 1 здесь неявно преобразуется внутри функции в переменную пустого типа интерфейса Эта переменная содержит две части информации: 1 Динамический тип этой переменной (для int) и динамическое значение (для 1) ;At последнийTypeOf
Возвращаемое значениеreflect.Type
тип (который мы называемобъект типа отражения), чтобы можно было вызвать метод вышеуказанного интерфейса Type для получения необходимой информации о переменных.
- Когда тип отраженного объекта является примитивным типом данных:
func main() {
var s string
rString := reflect.TypeOf(s)
fmt.Println(rString) //string
fmt.Println(rString.Name()) //string,返回表示类型名称的字符串
fmt.Println(rString.Kind()) //string,返回 reflect.Kind 类型的常量
}
- Когда тип отраженного объекта является типом указателя:
type Dog struct {
Name string
Age int
}
func main() {
dogPtr := &Dog{"doggy"}
rDogPtr := reflect.TypeOf(dogPtr)
fmt.Println(rDogPtr.Name()) // 为空
fmt.Println(rDogPtr.Kind()) // ptr
// Elem()可以获取指针指向的实际变量
rDog := rDogPtr.Elem()
fmt.Println(rDogPtr.Name()) // Dog
fmt.Println(rDogPtr.Kind()) // struct
}
Можно обнаружить, что когда объект отражения получен из указателя, его нельзя использовать напрямую.Name()
иKind()
, так что можно получить только информацию об указателе. В это время вы можете использоватьElem()
Получить фактическую переменную, на которую указывает указатель.
- Когда тип объекта отражения является типом структуры:
Если тип объекта отражения является структурой, вы можете передатьNumField()
иField()
метод для получения сведений о членах структуры.
type Dog struct {
Name string
Age int
}
func main() {
dog := Dog{"doggy", 2}
rDog := reflect.TypeOf(dog)
fmt.Printf("%v ", rDog.Name()) // Dog
fmt.Println(rDog.Kind()) // struct
for index := 0; index < rDog.NumField(); index++ {
fmt.Printf("%v ", rDog.Field(index).Name)
fmt.Println(rDog.Field(index).Type)
}
}
Рабочий вывод:
Dog struct
Name string
Age int
reflect.Value
в отражающем пакетеValue
Типы определяются следующим образом:
type Value struct {
// typ holds the type of the value represented by a Value.
typ *rtype
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer
// flag holds metadata about the value.
flag
}
можно увидетьValue
Типы содержат указатель типа, указатель значения и информацию о флаге. в то же времяValue
Типы также имеют множество методов, среди которых используются методы получения значения:
func (v Value) Int() int64 // 获取int类型值,如果 v 值不是有符号整型,则 panic。
func (v Value) Uint() uint64 // 获取unit类型的值,如果 v 值不是无符号整型(包括 uintptr),则 panic。
func (v Value) Float() float64 // 获取float类型的值,如果 v 值不是浮点型,则 panic。
func (v Value) Complex() complex128 // 获取复数类型的值,如果 v 值不是复数型,则 panic。
func (v Value) Bool() bool // 获取布尔类型的值,如果 v 值不是布尔型,则 panic。
func (v Value) Len() int // 获取 v 值的长度,v 值必须是字符串、数组、切片、映射、通道。
func (v Value) Cap() int // 获取 v 值的容量,v 值必须是数值、切片、通道。
func (v Value) Index(i int) reflect.Value // 获取 v 值的第 i 个元素,v 值必须是字符串、数组、切片,i 不能超出范围。
func (v Value) Bytes() []byte // 获取字节类型的值,如果 v 值不是字节切片,则 panic。
func (v Value) Slice(i, j int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = v.Cap() - i。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) Slice3(i, j, k int) reflect.Value // 获取 v 值的切片,切片长度 = j - i,切片容量 = k - i。
// i、j、k 不能超出 v 的容量。i <= j <= k。
// v 必须是字符串、数值、切片,如果是数组则必须可寻址。i 不能超出范围。
func (v Value) MapIndex(key Value) reflect.Value // 根据 key 键获取 v 值的内容,v 值必须是映射。
// 如果指定的元素不存在,或 v 值是未初始化的映射,则返回零值(reflect.ValueOf(nil))
func (v Value) MapKeys() []reflect.Value // 获取 v 值的所有键的无序列表,v 值必须是映射。
// 如果 v 值是未初始化的映射,则返回空列表。
func (v Value) OverflowInt(x int64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是有符号整型。
func (v Value) OverflowUint(x uint64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是无符号整型。
func (v Value) OverflowFloat(x float64) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是浮点型。
func (v Value) OverflowComplex(x complex128) bool // 判断 x 是否超出 v 值的取值范围,v 值必须是复数型。
Для метода заданного значения:
func (v Value) SetUint(x uint64) // 设置无符号整型的值
func (v Value) SetFloat(x float64) // 设置浮点类型的值
func (v Value) SetComplex(x complex128) //设置复数类型的值
func (v Value) SetBool(x bool) //设置布尔类型的值
func (v Value) SetString(x string) //设置字符串类型的值
func (v Value) SetLen(n int) // 设置切片的长度,n 不能超出范围,不能为负数。
func (v Value) SetCap(n int) //设置切片的容量
func (v Value) SetBytes(x []byte) //设置字节类型的值
func (v Value) SetMapIndex(key, val reflect.Value) //设置map的key和value,前提必须是初始化以后,存在覆盖、不存在添加
func (v Value) Set(x Value) // 将v的持有值修改为x的持有值。如果v.CanSet()返回假,会panic。x的持有值必须能直接赋给v持有值的类型。
Другие методы:
结构体相关:
func (v Value) NumField() int // 获取结构体字段(成员)数量
func (v Value) Field(i int) reflect.Value //根据索引获取结构体字段
func (v Value) FieldByIndex(index []int) reflect.Value // 根据索引链获取结构体嵌套字段
func (v Value) FieldByName(string) reflect.Value // 根据名称获取结构体的字段,不存在返回reflect.ValueOf(nil)
func (v Value) FieldByNameFunc(match func(string) bool) Value // 根据匹配函数 match 获取字段,如果没有匹配的字段,则返回零值(reflect.ValueOf(nil))
通道相关:
func (v Value) Send(x reflect.Value)// 发送数据(会阻塞),v 值必须是可写通道。
func (v Value) Recv() (x reflect.Value, ok bool) // 接收数据(会阻塞),v 值必须是可读通道。
func (v Value) TrySend(x reflect.Value) bool // 尝试发送数据(不会阻塞),v 值必须是可写通道。
func (v Value) TryRecv() (x reflect.Value, ok bool) // 尝试接收数据(不会阻塞),v 值必须是可读通道。
func (v Value) Close() // 关闭通道
函数相关
func (v Value) Call(in []Value) (r []Value) // 通过参数列表 in 调用 v 值所代表的函数(或方法)。函数的返回值存入 r 中返回。
// 要传入多少参数就在 in 中存入多少元素。
// Call 即可以调用定参函数(参数数量固定),也可以调用变参函数(参数数量可变)。
func (v Value) CallSlice(in []Value) []Value // 调用变参函数
Так же мы можем пройтиreflect.ValueOf
Принимает любой тип интерфейса{} и возвращает соответствующий динамический типreflect.Value
:
v := reflect.ValueOf(2)
fmt.Println(v) // 2
fmt.Println(v.String()) // <int Value>
посмотриreflect.ValueOf
Код реализации:
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
escapes () включает в себя выделение объектов в стеке и куче и анализ escape.Если вам интересно, вы можете прочитать серию статей, написанных Уильямом Кеннеди:Escape-анализ механизма языка Go
иreflect.TypeOf
похожий,ValueOf
Тип параметра функции — интерфейс {}, а входной параметр неявно преобразуется в переменную пустого типа интерфейса внутри функции и, наконец, возвращаетValue
объект иreflect.ValueOf
возвращаемое значение такжеобъект типа отражения.
можно заметитьValue
Объект также содержит информацию о типе фактического значения черезValue
изType()
Метод вернет соответствующийreflect.Type
:
v := reflect.ValueOf(2)
t := v.Type()
fmt.Println(t) // int
fmt.Println(t.String()) // int
пройти черезrelfect.Value
Получить информацию о фактических переменных
Теперь мы знаем, что поreflect.ValueOf
можетпеременная типа интерфейсапреобразовать впеременная типа отражения, конечно, мы также можем пройтиreflect.Value.Interface
Метод обратный, а затем фактическое значение получается путем утверждения:
v := reflect.ValueOf(2)
i := v.Interface()
if num, ok := i.(int); ok { // 类型断言
fmt.Println(num)
}
Но обычно в практических сценариях мы на самом деле не знаем тип исходного значения, здесь нам нужно использоватьreflect.Type
иreflect.Value
Метод исследует информацию об исходном значении. Вот пример для иллюстрации:
package main
import (
"fmt"
"reflect"
)
type Dog struct {
Name string
Age int
}
func (dog *Dog) Eat() {
fmt.Printf("%s is eating.", dog.Name)
}
func (dog *Dog) Run() {
fmt.Printf("%s is running.", dog.Name)
}
func (dog Dog) Sleep() {
fmt.Printf("%s is sleeping.", dog.Name)
}
func (dog Dog) Jump() {
fmt.Printf("%s is jumping.", dog.Name)
}
func main() {
doggy := Dog{"doggy", 2}
checkFieldAndMethod(doggy)
fmt.Println("")
tommy := &Dog{"tommy", 2}
checkFieldAndMethod(tommy)
}
func checkFieldAndMethod(input interface{}) {
inputType := reflect.TypeOf(input)
fmt.Println("Type of input is :", inputType.Name())
inputValue := reflect.ValueOf(input)
fmt.Println("Value of input is :", inputValue)
// 如果input原始类型时指针,通过Elem()方法或者Indirect()获取指针指向的值
if inputValue.Kind() == reflect.Ptr {
inputValue = inputValue.Elem()
// inputValue = reflect.Indirect(inputValue)
fmt.Println("Value input points to is :", inputValue)
}
//使用NumField()得到结构体中字段的数量,遍历得到字段的值Field(i)和类型Field(i).Type()
for i := 0; i < inputValue.NumField(); i++ {
field := inputValue.Type().Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 获取方法
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
Вывод после запуска:
Type of input is : Dog
Value of input is : {doggy 2}
Name: string = doggy
Age: int = 2
Jump: func(main.Dog)
Sleep: func(main.Dog)
Type of input is :
Value of input is : &{tommy 2}
Value input points to is : {tommy 2}
Name: string = tommy
Age: int = 2
Eat: func(*main.Dog)
Jump: func(*main.Dog)
Run: func(*main.Dog)
Sleep: func(*main.Dog)
Шаги для получения примитивных типов значений и методов с использованием отражения следующие:
- Определите, является ли исходное значение переменной значения или переменной указателя, если это переменная указателя, передайте
Elem()
метод илиIndirect()
Получить значение, на которое указывает указатель; - использовать
NumField()
Получите количество полей в структуре, пройдите, чтобы получить значение поляField(i)
и введитеField(i).Type()
; - использовать
NumMethod()
Получите метод структуры, перейдите, чтобы получить имя и тип метода.
Кроме того, при использованииreflect.Value
процесс иногдаElem()
Методы иIndirect()
Я немного запутался и не могу понять разницу между этими двумя методами.Вот резюме:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value
// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value.
// If v is not a pointer, Indirect returns v.
func Indirect(v Value) Value
-
Elem
Возвращает значение, удерживаемое интерфейсом, хранимым vValue
инкапсуляция или значение, на которое указывает указатель, удерживаемый vValue
упаковка. если vKind
нетInterface
илиPtr
встречаpanic
; если v содержит значениеnil
, вернусьValue
нулевое значение. -
Indirect
возвращает значение, на которое указывает указатель, удерживаемый vValue
упаковка. если v содержит значениеnil
, вернусьValue
нулевое значение. Если переменная, содержащаяся в v, не является указателем, то возвращается исходное значение v.
То есть, когда переменная, хранимая v, является указателем,Elem()
Методы иIndirect()
эквивалентны.
Внимательные читатели могут обнаружить, что для переменных-значений и переменных-указателей существуют некоторые различия в методах переменных, полученных в результате отражения, и этот вопрос оставлен читателям для размышления.
пройти черезrelfect.Value
Изменить информацию фактической переменной
когда прошлоrelfect.Value
Изменение информации фактической переменной обычно используется для отражения следующих методов объектов-значений:
func (v Value) Elem() Value
//Elem()返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装,类似于*操作,此时的Value表示的是Value的元素且可以寻址。
func (v Value) Addr() Value
//Addr()返回一个持有指向v变量地址的指针的Value封装,类似于&操作。
func (v Value) CanAddr() bool
//CanAddr()返回是否可以获取v持有值的指针。可以获取指针的值被称为可寻址的。
func (v Value) CanSet() bool
//CanSet()返回v持有的值是否可以被修改
Однако стоит отметить, что не всеreflect.Value
Отраженные значения типов можно модифицировать, рассмотрим следующий пример:
package main
import(
"fmt"
"reflect"
)
func main() {
a := 1
rA := reflect.ValueOf(a)
fmt.Println(rA.CanSet()) //false
rAptr := reflect.ValueOf(&a)
rA2 := rAptr.Elem()
fmt.Println(rA2.CanSet()) //true
rA2.SetInt(2)
fmt.Println(rA2.Int()) //2
}
Есть два условия для изменения значения переменной типа отражения:
- Значение переменной типа отражения равно
addressable
, вы можете получить адрес; - Значение переменной типа отражения берется из производного поля.
Некоторые переменные изменяемого типа отражения являются адресуемыми, некоторые нет:
package main
import (
"reflect"
"fmt"
)
func main() {
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
d := c.Elem()
fmt.Println(a.CanAddr()) // false
fmt.Println(b.CanAddr()) // false
fmt.Println(c.CanAddr()) // false
fmt.Println(d.CanAddr()) // true
}
Для переменной x, не являющейся указателем, передайтеreflect.ValueOf(x)
возвращениеreflect.Value
является нежелательным адресом. Но для d он генерируется путем разыменования c, указывающего на другую переменную, поэтому он является адресуемым. Мы можем позвонитьreflect.ValueOf(&x).Elem()
, чтобы получить значение отражения адресуемого адреса, соответствующего x.
Для переменных структурного типа, если поля-члены не экспортированы, к ним можно получить доступ, но нельзя изменить путем отражения:
package main
import (
"fmt"
"reflect"
)
type Dog struct {
Name string
Age int
sex string
}
func main() {
rDog := reflect.ValueOf(&Dog{}).Elem()
vAge := rDog.FieldByName("Age")
vAge.SetInt(1)
vSex := rDog.FieldByName("sex")
vSex.SetString("male")
}
Ошибка при запуске: значение, используемое SetString, получено из неэкспортированного поля.
panic: reflect: reflect.Value.SetString using value obtained using unexported field
Чтобы можно было изменить это значение, поле необходимо экспортировать. Просто напишите заглавной первую букву члена пола в типе Dog.
Изменить адресуемыйreflect.Value
Удерживаемое значение переменной, в дополнение к методам серии отражения Set, также можно изменить, получив указатель на фактическое значение из переменной типа отражения:
package main
import (
"reflect"
"fmt"
)
func main() {
x := 1
v := reflect.ValueOf(&x).Elem()
px := v.Addr().Interface().(*int)
*px = 2
fmt.Print(x) //2
}
первый звонокAddr()
метод, который возвращает указатель, содержащий указатель на переменнуюValue
; затем вValue
звонитьInterface()
метод, который возвращаетinterface{}
, который содержит указатель на переменную; наконец, обычный указатель получается с помощью утверждения типа для изменения значения переменной.
Вызов функции через отражение
Если объект значения отражения (reflect.Value
) когда тип удерживаемого значения является функцией, вы можете передатьreflect.Value
вызвать эту функцию.
func (v Value) Call(in []Value) []Value
Call
Метод вызывает функцию, хранящуюся в v, с входным аргументом. Параметр in представляет собой срез объекта отраженного значения, т.е.[]reflect.Value
; Когда вызов завершается, возвращаемое значение функции передается через[]reflect.Value
возвращение.
package main
import(
"fmt"
"reflect"
)
func add(a, b int) int {
return a + b
}
func main() {
// 将函数add包装为反射值对象
funcValue := reflect.ValueOf(add)
// 构造函数add的参数, 传入两个整型值
paramList := []reflect.Value{reflect.ValueOf(5), reflect.ValueOf(10)}
// 反射调用函数Call()
retList := funcValue.Call(paramList)
// 获取第一个返回值, 取整数值
fmt.Println(retList[0].Int()) //返回 15
}
Если вам нужно вызвать метод структуры через отражение, вы можете использоватьMethodByName
способ завершения:
func (v Value) MethodByName(name string) Value
//返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
Пример:
package main
import(
"fmt"
"reflect"
)
type Dog struct {
Name string
Age int
}
func (dog *Dog) SetName(name string){
dog.Name = name
}
func main() {
dog := Dog{}
rDog := reflect.ValueOf(&dog)
paramList1 := []reflect.Value{reflect.ValueOf("doggy")}
rDog.MethodByName("SetName").Call(paramList1)
fmt.Println(dog.Name) //doggy
}
Стоит отметить, что процесс рефлективного вызова функции требует построения большого количестваreflect.Value
И промежуточные переменные, значения параметров функции проверяются по одному, а вызывающие параметры нужно копировать в память параметров вызывающей функции. После вызова вам также необходимо преобразовать возвращаемое значение вreflect.Value
, пользователю также необходимо получить из него значение вызова. Итак, отражение вызывает функциюПроблемы с производительностью особенно заметныНе рекомендуется использовать рефлексивный вызов функции.
Суммировать
Эта статья знакомит с языком Gointerface
Настоящим вводятся определение, использование и побочные эффекты , иreflect
, подробно с многочисленными примерамиreflect
концепция, черезreflect
Использование для получения значения, для изменения значения и использования для вызова функции. Содержание можно сказать достаточно подробное и конкретное, в процессе у автора также происходит более глубокое понимание этой части знаний, и я надеюсь, что мне посчастливится немного помочь читателям.
использованная литература
[Документ стандартной библиотеки GOLANG]