Рефлексия позволяет работать с типами данных, которые не существуют на момент написания кода, но могут существовать в будущем, когда мы используем существующий пакет с пользовательскими типами данных.
Кроме того, рефлексия может пригодиться, когда необходимо работать с типами данных, которые не реализуют общий интерфейс и, следовательно, имеют необычное или неизвестное поведение. Это не означает плохое или ошибочное поведение, а просто необычное, такое как определяемая пользователем структура.
Наиболее полезными частями пакета reflect
являются два типа данных: reflect.Value и reflect.Type. В частности‚ reflect.Value используется для хранения значений любого типа, тогда как reflect.Type служит для представления Go-типов. Существуют две функции: reflect.TypeOf() и reflect.valueOf(), которые возвращают reflect.Type и reflect.Value соответственно. Обратите внимание, что reflect.TypeOf() возвращает фактический тип переменной‚ и если мы исследуем структуру, то она вернет имя структуры. Поскольку структуры играют ключевую роль в Go, пакет reflect содержит метод reflect.NumField(), предназначенный для перечисления количества полей в структуре, а также метод Field(), позволяющий получать значение reflect.Value определенного поля структуры.
Пакет reflect также определяет тип данных reflect.Kind, который используется для представления определенного типа данных переменной: int, struct и т. д. В документации к пакету reflect перечислены все возможные значения типа данных reflect.Kind. Функция Kind() возвращает вид переменной. Наконец, методы Int() и String() возвращают целое и строковое значения reflect.Value соответственно.
Следующая утилита показывает, как использовать рефлексию для обнаружения внутренней структуры и полей переменной Go-структуры. Введите ее и сохраните как reflection.go:
package main import ( "fmt" "reflect" ) type Secret struct { Username string Password string } type Record struct { Field1 string Field2 float64 Field3 Secret } func main() { A := Record{"String value", -12.123, Secret{"Mihalis", "Tsoukalos"}} r := reflect.ValueOf(A) // Здесь возвращается значение reflect.Value переменной A. fmt.Println("String value:", r.String()) iType := r.Type() // Используя Type(), мы получаем тип данных переменной — в данном случае переменной A. fmt.Printf("i Type: %s\n", iType) fmt.Printf("The %d fields of %s are\n", r.NumField(), iType) // Цикл for выше позволяет посетить все поля структуры и изучить их характеристики. for i := 0; i < r.NumField(); i++ { // Оператор fmt.Printf() возвращает имя, тип данных и значение полей. fmt.Printf("\t%s ", iType.Field(i).Name) fmt.Printf("\twith type: %s ", r.Field(i).Type()) fmt.Printf("\tand value _%v_\n", r.Field(i).Interface()) // Проверяем, есть ли в значении другие структуры k := reflect.TypeOf(r.Field(i).Interface()).Kind() // Чтобы проверить тип данных переменной с помощью строки, нам нужно сначала преобразовать тип данных в string. if k.String() == "struct" { fmt.Println(r.Field(i).Type()) } // Тоже что и выше, но с использованием внутреннего значение Struct if k == reflect.Struct { fmt.Println(r.Field(i).Type()) } } }