Рефлексия
Рефлексия — это механизм, который позволяет проверять структуру самого языка во время выполнения. Он может гибко решать некоторые проблемы, но при этом имеет очевидные недостатки, такие как проблемы с производительностью. В Go рефлексия тесно связана с interface{}. В значительной степени, где бы ни появлялся interface{}, там будет и рефлексия. API рефлексии в Go предоставляется стандартным пакетом reflect.
Интерфейсы
Прежде чем начать, кратко рассмотрим два интерфейса в пакете runtime. В Go интерфейсы по сути являются структурами. Go во время выполнения разделяет интерфейсы на две большие категории: интерфейсы без набора методов и интерфейсы с набором методов. Для интерфейсов, содержащих набор методов, во время выполнения используется следующая структура iface:
type iface struct {
tab *itab // содержит тип данных, тип интерфейса, набор методов и т.д.
data unsafe.Pointer // указатель на значение
}Для интерфейсов без набора методов во время выполнения используется структура eface:
type eface struct {
_type *_type // тип
data unsafe.Pointer // указатель на значение
}Обе эти структуры имеют соответствующие типы структур в пакете reflect. iface соответствует nonEmptyInterface:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // статический тип интерфейса
typ *rtype // динамический конкретный тип
hash uint32 // хэш типа
_ [4]byte
fun [100000]unsafe.Pointer // набор методов
}
word unsafe.Pointer // указатель на значение
}А eface соответствует emptyInterface:
type emptyInterface struct {
typ *rtype // динамический конкретный тип
word unsafe.Pointer // значение указателя
}Для этих двух типов официальная документация даёт чёткие определения:
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
Выше упоминался термин «динамический конкретный тип» (оригинал: dynamic concrete type). Прежде всего, Go — это на 100% статически типизированный язык. Слово «статический» проявляется в том, что абстрактный тип интерфейса, представленный внешне, неизменен, а «динамический» означает, что конкретный тип реализации, хранящийся внутри интерфейса, может изменяться. На этом достаточно знаний о принципах работы интерфейсов для последующего изучения рефлексии.
Мост
В пакете reflect есть тип интерфейса reflect.Type для представления типов в Go и тип структуры reflect.Value для представления значений в Go:
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}В приведённом коде опущено много деталей. Пока достаточно знать о существовании этих двух типов. Все операции рефлексии в Go основаны на этих двух типах. Пакет reflect предоставляет две функции для преобразования типов Go в вышеупомянутые два типа для выполнения операций рефлексии: функция reflect.TypeOf:
func TypeOf(i any) TypeИ функция reflect.ValueOf:
func ValueOf(i any) ValueМожно видеть, что параметры обеих функций имеют тип any, то есть псевдоним interface{}. Если вы хотите выполнить операции рефлексии, нужно сначала преобразовать тип к interface{}. Именно поэтому ранее упоминалось, что рефлексия не обходится без пустого интерфейса. Не строго говоря, пустой интерфейс — это мост, соединяющий систему типов Go и рефлексию. Следующая схема наглядно описывает этот процесс.

TIP
В дальнейшем для удобства будем использовать псевдоним any вместо interface{}.
Ядро
В Go есть три классических закона рефлексии, которые,结合 с вышеизложенным, легко понять:
Рефлексия может преобразовать переменную типа
interface{}в объект рефлексии.Рефлексия может восстановить объект рефлексии в переменную типа
interface{}.Чтобы изменить объект рефлексии, его значение должно быть устанавливаемым.
Эти три закона — ядро рефлексии в Go. Когда нужно получить доступ к информации о типе, используется reflect.TypeOf, а когда нужно изменить значение рефлексии, используется reflect.ValueOf.
Типы
reflect.Type представляет тип в Go. Использование функции reflect.TypeOf() позволяет преобразовать переменную в reflect.Type. Пример кода:
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}Вывод:
stringKind
Для Type в Go внутренне используется reflect.Kind для представления базовых типов. По сути это беззнаковое целое число uint:
type Kind uintПакет reflect использует Kind для перечисления всех базовых типов в Go:
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)Тип Kind реализует только метод String() интерфейса Stringer. Этот тип имеет только один метод. Возвращаемое значение метода String() берётся из внутреннего slice:
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}type Type interface{
Kind() Kind
}С помощью Kind можно узнать, какой базовый тип хранится в пустом интерфейсе. Например:
func main() {
// Объявляем переменную типа any
var eface any
// Присваиваем значение
eface = 100
// Получаем тип через метод Kind
fmt.Println(reflect.TypeOf(eface).Kind())
}Вывод:
intElem
type Type interface{
Elem() Type
}Используя метод Type.Elem(), можно определить тип элемента, хранящегося в структуре данных типа any. Допустимые типы параметров: указатель, срез, массив, канал, отображение. В противном случае произойдёт panic. Пример кода:
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// Key() возвращает тип ключа map
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}Вывод:
string
intУказатель также можно понимать как контейнер. Для указателя использование Elem() вернёт тип элемента, на который он указывает. Пример кода:
func main() {
var eface any
// Присваиваем указатель
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// Получаем тип элемента, на который указывает указатель
vType := rType.Elem()
// Выводим путь к пакету
fmt.Println(vType.PkgPath())
// Выводим имя
fmt.Println(vType.Name())
}strings
BuilderДля массивов, срезов и каналов использование аналогично.
Size
type Type interface{
Size() uintptr
}Метод Size позволяет получить размер типа в байтах. Пример:
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}Вывод:
8
16
16
8
24TIP
Использование unsafe.Sizeof() даёт тот же эффект.
Comparable
type Type interface{
Comparable() bool
}Метод Comparable позволяет определить, можно ли сравнивать тип. Пример:
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}Вывод:
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}Метод Implements позволяет определить, реализует ли тип определённый интерфейс:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}Вывод:
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}Метод ConvertibleTo позволяет определить, можно ли преобразовать один тип в другой указанный тип:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}Вывод:
true
falseЗначения
reflect.Value представляет значение рефлектируемого интерфейса. Использование функции reflect.ValueOf() позволяет преобразовать переменную в reflect.Value. Пример кода:
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}Вывод:
hello world!Type
func (v Value) Type() TypeМетод Type позволяет получить тип рефлектируемого значения:
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Вывод:
intElem
func (v Value) Elem() ValueПолучение элемента рефлектируемого значения:
func main() {
num := new(int)
*num = 114514
// На примере указателя
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Вывод:
114514Указатели
Есть два способа получить указатель рефлектируемого значения:
// Возвращает указатель, представляющий адрес v
func (v Value) Addr() Value
// Возвращает uintptr, указывающий на исходное значение v, эквивалентно uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// Возвращает uintptr, указывающий на исходное значение v
// Только когда Kind v равен Chan, Func, Map, Pointer, Slice, UnsafePointer, иначе panic
func (v Value) Pointer() uintptr
// Возвращает unsafe.Pointer, указывающий на исходное значение v
// Только когда Kind v равен Chan, Func, Map, Pointer, Slice, UnsafePointer, иначе panic
func (v Value) UnsafePointer() unsafe.PointerПример:
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}Вывод:
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println рефлектирует тип параметра. Если тип reflect.Value, автоматически вызывается Value.Interface() для получения исходного значения.
Теперь с map:
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}Вывод:
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0Установка значения
func (v Value) Set(x Value)Если вы хотите изменить рефлектируемое значение через рефлексию, оно должно быть адресуемым. В этом случае следует изменять значение элемента через указатель, а не пытаться изменить значение элемента напрямую.
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// Получаем элемент, на который указывает указатель
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}Вывод:
114514
11Получение значения
func (v Value) Interface() (i any)Метод Interface() позволяет получить исходное значение рефлектируемого значения:
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Вывод:
helloФункции
Через рефлексию можно получить всю информацию о функции, а также вызвать функцию через рефлексию.
Информация
Получение всей информации о функции через тип рефлексии:
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// Вывод имени функции. У литеральных функций нет имени.
fmt.Println(rType.Name())
// Вывод количества параметров и возвращаемых значений
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// Вывод типа первого параметра
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// Вывод типа первого возвращаемого значения
fmt.Println(rResType.Kind())
}Вывод:
2 1
int
intВызов
Вызов функции через значение рефлексии:
func (v Value) Call(in []Value) []Valuefunc main() {
// Получаем рефлектируемое значение функции
rType := reflect.ValueOf(Max)
// Передаём массив параметров
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}Вывод:
50Структуры
Предположим, есть следующая структура:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}Доступ к полям
Структура reflect.StructField выглядит следующим образом:
type StructField struct {
// Имя поля
Name string
// Имя пакета
PkgPath string
// Имя типа
Type Type
// Tag
Tag StructTag
// Смещение поля в байтах
Offset uintptr
// Индекс
Index []int
// Является ли вложенным полем
Anonymous bool
}Есть два способа доступа к полям структуры: через индекс и через имя.
type Type interface{
Field(i int) StructField
}Пример доступа через индекс:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Вывод количества полей структуры
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}Вывод:
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}Пример доступа через имя:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Вывод количества полей структуры
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}Вывод:
4
money int falseИзменение полей
Если нужно изменить значение поля структуры, необходимо передать указатель на структуру. Ниже пример изменения поля:
func main() {
// Передаём указатель
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Получаем поле
name := rValue.FieldByName("Name")
// Изменяем значение поля
if (name != reflect.Value{}) { // Если возвращён reflect.Value{}, значит поле не существует
name.SetString("jack")
}
// Вывод структуры
fmt.Println(rValue.Interface())
}Вывод:
{jack 0 0}Для изменения приватного поля структуры требуются дополнительные операции:
func main() {
// Передаём указатель
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Получаем приватное поле
money := rValue.FieldByName("money")
// Изменяем значение поля
if (money != reflect.Value{}) {
// Создаём указатель на неэкспортируемое поле структуры
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// Получаем элемент, на который указывает указатель, то есть поле для изменения
field := p.Elem()
// Изменяем значение
field.SetInt(164)
}
// Вывод структуры
fmt.Printf("%+v\n", rValue.Interface())
}Доступ к Tag
Получив StructField, можно напрямую обратиться к его Tag:
// Если не существует, ok равно false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// Если не существует, возвращает пустую строку
func (tag StructTag) Get(key string) stringПример:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}Вывод:
name true
nameДоступ к методам
Доступ к методам аналогичен доступу к полям, только сигнатуры функций немного отличаются. Структура reflect.Method:
type Method struct {
// Имя метода
Name string
// Имя пакета
PkgPath string
// Тип метода
Type Type
// Функция, соответствующая методу, первый параметр — получатель
Func Value
// Индекс
Index int
}Пример доступа к информации о методе:
func main() {
// Получаем тип рефлексии структуры
rType := reflect.TypeOf(new(Person)).Elem()
// Вывод количества методов
fmt.Println(rType.NumMethod())
// Перебираем и выводим информацию о методах
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}Вывод:
1
0 Talk func(main.Person, string) string trueЕсли нужно получить детали параметров и возвращаемых значений метода, можно использовать Method.Func. Процесс аналогичен доступу к информации о функции. Немного изменим приведённый выше код:
func main() {
// Получаем тип рефлексии структуры
rType := reflect.TypeOf(new(Person)).Elem()
// Вывод количества методов
fmt.Println(rType.NumMethod())
// Перебираем и выводим информацию о методах
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("Параметры метода")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("Возвращаемые значения метода")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}Можно видеть, что первый параметр — main.Person, то есть тип получателя:
1
0 Talk func(main.Person, string) string true
Параметры метода
main.Person
string
Возвращаемые значения метода
stringВызов метода
Вызов метода аналогичен вызову функции, причём не нужно вручную передавать получателя. Пример:
func main() {
// Получаем значение рефлексии структуры
rValue := reflect.ValueOf(new(Person)).Elem()
// Вывод количества методов
fmt.Println(rValue.NumMethod())
// Перебираем и выводим информацию о методах
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// Вызываем метод и получаем возвращаемое значение
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// Перебираем и выводим возвращаемые значения
for _, re := range res {
fmt.Println(re.Interface())
}
}
}Вывод:
1
hello,reflect!Создание
Через рефлексию можно создавать новые значения. Пакет reflect предоставляет различные удобные функции для специальных типов.
Базовые типы
// Возвращает указатель на рефлектируемое значение
func New(typ Type) ValueНа примере string:
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!Структуры
Создание структуры также использует функцию reflect.New:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// Создаём рефлектируемое значение структуры
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}Вывод:
{ 0 0}Срезы
Создание среза через рефлексию:
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// Создаём рефлектируемое значение среза
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// Перебираем и присваиваем значения
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}[0 1 2 3 4 5 6 7 8 9]Map
Создание Map через рефлексию:
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
// Создаём рефлектируемое значение map
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// Устанавливаем значение
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]Каналы
Создание канала через рефлексию:
func MakeChan(typ Type, buffer int) Valuefunc main() {
// Создаём рефлектируемое значение канала
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}Функции
Создание функции через рефлексию:
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// Передаём тип обёртки и тело функции
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}Вывод:
func(int)
1024Полное равенство
reflect.DeepEqual — функция, предоставляемая пакетом рефлексии для определения полного равенства двух переменных. Сигнатура:
func DeepEqual(x, y any) boolЭта функция обрабатывает каждый базовый тип. Ниже приведены способы сравнения для некоторых типов:
- Массивы: каждый элемент массивов полностью равен
- Срезы: полностью равны, если оба
nil, или если оба не пусты и элементы в пределах длины полностью равны - Структуры: все поля полностью равны
- Отображения: полностью равны, если оба
nil; если оба неnil, то значения, отображаемые каждым ключом, полностью равны - Указатели: указывают на один и тот же элемент или указывают на полностью равные элементы
- Интерфейсы: конкретные типы интерфейсов полностью равны
- Функции: полностью равны только если оба
nil, иначе не равны
Ниже приведены примеры:
Срезы
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Вывод:
trueСтруктуры
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}Вывод:
false
false
true