Перевел статью об отражении в официальном блоге Go:
Введение
В информатике отражение — это способность обнаруживать собственную структуру (тип) во время выполнения, а отражение составляет основу метапрограммирования и источник путаницы.
В этой статье мы попытаемся разъяснить, как работает отражение в Go, каждый язык имеет свою модель отражения (обычно Java), а многие языки даже не поддерживают отражение, поэтому в этой статье я просто опишу отражение в Go.
Типы и интерфейсы
Поскольку вся модель отражения построена на системе типов, давайте сначала рассмотрим типы в Go.
Go — статически типизированный язык, любая переменная имеет явный тип во время компиляции, напримерint、float32、*MyType, []byte
и т.п. типа...
type MyInt int
var i int
var j MyInt
Переменнаяi
имеет типint
,Переменнаяj
имеет типMyInt
. Они оба имеют различные статические типы, но иначе имеют одинаковый базовый типint
. Поскольку статические типы различаются, их необходимо преобразовать, прежде чем их можно будет назначить.
Тип интерфейса — очень важная категория в системе типов, которая представляет собой сжатый набор методов. Переменная интерфейса может хранить любое значение, если это значение реализует соответствующий набор методов интерфейса.io
в упаковкеio.Reader 和 io.Writer
Интерфейсы — хорошо известный пример.
// Reader is the interface that wraps the basic Read method.
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer is the interface that wraps the basic Write method.
type Writer interface {
Write(p []byte) (n int, err error)
}
любого типа, если он реализуетRead
илиWrite
способ реализоватьio.Reader
илиio.Writer
интерфейс. Значение: тип интерфейсаio.Reader
Может быть назначена любая реализацияRead
Тип метода.
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
выяснить переменныеr
Внутреннее поведение — очень важная вещь, прежде всегоr
Тип всегдаio.Reader
Причина очень проста, Go - статически напечатанный язык,r
Тип уже определен при компиляцииio.Reader
.
Важным примером типа интерфейса является пустой интерфейс.interface{}
:
interface{}
Пустой набор методов означает, что любой тип реализует пустой интерфейс,Любому типу может быть присвоено значение.
Некоторые люди говорят, что интерфейсы динамически типизированы, но это не так, они статически типизированы. Переменная типа интерфейса всегда имеет фиксированный статический тип, даже если значение, хранящееся в интерфейсе во время выполнения, имеет другой тип (тип реализует набор методов интерфейса).
Нам необходимо понимать эти концепции, потому что отражение и интерфейсы тесно связаны.
значение интерфейса
Расс Кокс написал статьюGo Data Structures: InterfacesПодробно объясняются интерфейсные значения языка Go. Опять же, не повторяя понятий в статье, вот краткое содержание статьи:
Переменная типа интерфейса хранит пару значений:
- value: фактическое значение, присвоенное переменной типа интерфейса;
- type: Информация о типе фактического значения.
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
Переменная типа интерфейсаr
Включают(value, type)
правильно(tty, *os.File)
. тип*os.File
Способы добиться большего, чемRead
, даже если текущий интерфейс предоставляет толькоRead
метод, значение внутри интерфейса несет всю информацию о типе значения, поэтому мы можем реализовать следующие операции (информация о типе, естественно, утверждается):
var w io.Writer
w = r.(io.Writer)
Приведенное выше выражение присваивания называется утверждением типа, его утверждениеr
Внутренне хранится в переменных интерфейса(value, type)
выполнитьio.Writer
интерфейс, поэтому мы можем назначить егоw
. После того, как задание выполнено,w
Включают(tty, *os.File)
, С намиr
так же, как видно в. Статический тип интерфейса определяет, какие методы могут быть вызваны через переменную интерфейса, даже если внутренне сохраненный(value, type)
Имеет больший набор методов.
Двигаясь дальше, мы также можем сделать это:
var empty interface{}
empty = w
наш пустой интерфейсempty
будет по-прежнему хранить то же самое внутри(tty, *os.File)
.这意味着空接口可以存储任何值并拥有我们需要的所有信息。
Там нет утверждения типа при назначении значения для пустого интерфейса, потому что любое значение удовлетворяет интерфейсу воздуха,w
Очевидно, реализует пустой интерфейс (набор методов является надмножеством пустого интерфейса). в то время как вышеReader
преобразованныйWriter
не то же самое, нам нужно явно использовать утверждение типа, потому чтоReader
интерфейс неWriter
Расширенный набор интерфейсов.
Важной деталью является то, что интерфейс всегда хранится (значение, тип бетона) и не может хранить (значение, тип интерфейса), а интерфейс не хранит значения интерфейса!
Теперь мы готовы изучать отражение.
первый закон отражения
Отражение извлекает объекты отражения из значений интерфейса.
В самом основном понятии,Отражение — это просто механизм для определения типа и значения, хранящегося в интерфейсе.. Итак, нам нужно понятьreflectдва вида в упаковкеTypeиValue. Эти два типа обеспечивают доступ к внутреннему хранению переменных интерфейса и обеспечивают две простые функцииTypeOf
иValueOf
Получить из переменной интерфейсаType
иValue
(отValue
получитьType
Это тоже очень простая вещь, мы временно оставляем их концептуально разделенными)
ДавайтеTypeOf
Начинать:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}
вывод:
type: float64
Вам может быть интересно, где находится интерфейс, похоже, он только проходитfloat64
Типx
переменная в качестве параметраTypeOf
функции, а не переменные интерфейса. ФактическиTypeOf
Параметр функции подписи пустой интерфейс,x
Сначала он будет назначен пустой интерфейс, а затем передан как параметр функции,TypeOf
Функция внутренне обрабатывает пустой интерфейс, чтобы восстановить информацию о типе.Type
.
// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
ValueOf
Аналогично получается и функцияValue
переменная типа.
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())
вывод:
value: <float64 Value>
позвонить напрямую
String
метод, потому что по умолчаниюfmt
Пакеты погружаются прямо вValue
Отображает внутреннее истинное значение (3.4).
Type
иValue
оба содержат множество методов для их обнаружения и манипулирования ими, важным из которых являетсяValue
изType
метод возвращает соответствующийType
введите значение. Другой важный способ - иметь обаKind
метод возвращает константный примитивный тип (Uint、Float64、Slice
Ждать). как правилоValue
ВверхInt
,Float
Роль функции заключается в извлечении значения, хранящегося внутри.
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
вывод:
type: float64
kind is float64: true
value: 3.4
иметь что-тоSetInt
иSetFloat
методы класса, их использование должно быть понятонастраиваемыйконцепций, третий закон отражения ниже говорит о них подробно.
В библиотеке отражений есть несколько концепций, о которых стоит поговорить отдельно.
- Чтобы API был простым и
Value
Типgetter
иsetter
Наборы методов могут оперировать относительно большими значениями, используются все целые числа без знака.int64
как параметр и возвращаемое значение. какInt
метод возвращаетint64
значение типа,SetInt
использоватьint64
тип параметра.
var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type()) // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint()) // v.Uint returns a uint64.
-
Kind
Метод возвращает базовый тип, соответствующий статическому типу, например следующийx
Статический типMyInt
тип, базовый типreflect.Int
.
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
второй закон отражения
Отражение извлекает значения интерфейса из объекта отражения.
Точно так же, как закон физического рефлекса, значение границы раздела может быть получено из обратного направления отражающего объекта.
пройти черезValue
изInterface
Метод может восстановить значение интерфейса, на самом деле этот метод упаковываетtype
иvalue
Информация возвращается в пустой интерфейс.
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
В результате мы можем добиться:
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
путем отражения объектаv
Распечататьfloat64
ценность.
мы можем использоватьfmt.Println、fmt.Printf
Лучше работают и т. д. функции, которые принимают в качестве аргументов пустые значения интерфейса и распаковывают эти аргументы, как вы узнали выше. Итак, если вы хотите печатать напрямуюreflect.Value
нужно использовать контентInterface
Функция передается после получения значения интерфейса.
fmt.Println(v.Interface())
Почему бы просто не использоватьfmt.Println(v)
? так какv
даreflect.Value
Тип значения, который нам нужен, — это значение, которое фактически сохраняется.
fmt.Printf("value is %7.1e\n", v.Interface())
вывод
3.4e+00
Опять же, здесь нет необходимости использовать утверждения типов.v.Interface()
прибытьfloat64
Это связано с тем, что значение и тип, хранящиеся внутри пустого интерфейса,Printf
Внутри функция будет восстановлена.
короче,Interface
путьValueOf
обратный метод, за исключением того, что возвращаемое значение всегда имеет статический типinterface{}
.
третий закон отражения
Чтобы изменить объект отражения, значение должно быть устанавливаемым.
Статья 3. Закон очень легко запутать, а если начать понимать с первого принципа, то он проще.
Вот некоторые из них не работают, но стоит изучить код:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
Если вы разрешите эти коды, он будет генерироватьpanic
Ошибка.
panic: reflect.Value.SetFloat using unaddressable value
Ошибка не говорит о том, что значение 7.1 равноnot addressable
, но скажиv
не настраивается, настраиваетсяValue
важные свойства, не всеValue
все настраиваемые.
CanSet
Метод проверяет, является ли значение устанавливаемым.
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
вывод:
settability of v: false
в неустановленномValue
звонитьSet
Метод пойдет не так, так что же можно установить?
Установленность немного похожа на адресативность, строго говоря: ** Это свойство отражающего объекта, который может изменять значение, которое фактически создало отражающий объект. Установлена зависит от того, имеет ли отражательный объект исходное значение (указатель).
var x float64 = 3.4
v := reflect.ValueOf(x)
Когда мы передаем копию x вreflect.ValueOf
, поэтому пустое интерфейсное значение параметра сохраняется внутриx
копировать вместоx
сам.
v.SetFloat(7.1)
Следовательно, если этот оператор выполняется успешно, он не будет обновляться.x
,Несмотря на тоv
Похоже, сквозьx
Созданный. Но будет обновляться сохраненоValue
скопировать значение в , правдаx
не пострадал. Вышеупомянутая ситуация склонна к путанице и путанице, поэтому такое поведение определяется как недопустимое на уровне языка, и этой проблемы можно избежать, установив атрибуты на основе суждения.
Если приведенное выше выглядит странно, это не так, это просто странная оболочка для знакомых ситуаций (передача по значению и передача по указателю, вы можете изменить исходное значение только при получении указателя).
передача мыслиx
к функции.
f(x)
мы не ожидалиf
можно изменитьx
значение, так как мы переходим кf
даx
копия значения вместоx
сам. Если мы хотим изменить напрямуюx
, мы должны пройтиx
адрес (указатель).
f(&x)
Вышеупомянутый способ очень прост и понятен, и принцип отражения такой же. Если мы хотим изменить путем отраженияx
, мы должны передать указатель наValue
.
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
вывод:
type of p: *float64
settability of p: false
объект отраженияp
Все еще не устанавливается, но мы не хотим его изменятьp
Значение указателя, на самом деле то, что мы хотим изменить,*p
которыйp
значение, на которое указывает. нам нужно позвонитьElem
метод,Он косвенно принимает исходное значение через указатель и сохраняет результат в новомValue
возвращение в стоимости.
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
в настоящее времяv
является устанавливаемым объектом отражения.
settability of v: true
посколькуv
начать представлятьx
, мы наконец можем использоватьv.SetFloat
Модификация методаx
Значение:
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
вывод:
7.1
7.1
Хотя рефлексию немного сложно понять, все, что она делает, поддерживается на уровне языка, может быть.Value
иType
скроет случившееся. Просто бодрствуйте и обратите вниманиеValue
При изменении должен указывать на адрес.
Structs
В приведенном выше примереv
Просто указатель на примитивный тип, и более общая проблема заключается в изменении полей структуры, когда у нас есть указатель на структуру, мы можем изменить значения ее полей.
Ниже приведен простой пример разбора значения структуры. использоватьT
Создайте указатель типаValue
И поэтому могут быть изменены в последующемt
.
объявить и инициализироватьtypeOfT
в видеt
тип, и с помощью прямого методаNumField
иField
Извлеките имя поля, тип и значение.
-
Value
изField
все ещеValue
, и устанавливается; -
Type
изField
являетсяStructField
.
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}
вывод:
0: A int = 23
1: B string = skidoo
Еще одно знание о settable: только поля, начинающиеся с прописной буквы, являются setable (экспортируемые поля).
так какs
Содержит устанавливаемый объект отражения (Elem получает исходный объект), и мы можем изменять поля структуры.
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
вывод:
t is now {77 Sunset Strip}
о
- My Blog
- My Wechat: