리플렉션
리플렉션은 실행 시 언어 자체의 구조를 검사할 수 있는 메커니즘으로, 일부 문제에 유연하게 대응할 수 있게 해주지만 동시에 성능 문제와 같은 단점도 분명히 존재합니다. Go 에서 리플렉션은 interface{}와 밀접한 관련이 있으며, 크게 말해 interface{}가 나타나는 곳에는 리플렉션이 존재한다고 할 수 있습니다. Go 의 리플렉션 API 는 표준 라이브러리 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 패키지에는 Go 의 타입을 나타내는 reflect.Type 인터페이스 타입과 Go 의 값을 나타내는 reflect.Value 구조체 타입이 있습니다.
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 를 사용하여 Go 의 기본 타입을 나타내며, 본질적으로 부호 없는 정수 uint 입니다.
type Kind uintreflect 패키지는 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 타입은 Stringer 인터페이스의 String() 메서드만 구현했으며, 이 타입에도 이 메서드 하나뿐입니다. 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() TypeType 메서드는 리플렉션 값의 타입을 얻을 수 있습니다.
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
// v 의 원본 값을 가리키는 uintptr 반환, uintptr(Value.Addr().UnsafePointer()) 와 동일
func (v Value) UnsafeAddr() uintptr
// v 의 원본 값을 가리키는 uintptr 반환
// v 의 Kind 가 Chan, Func, Map, Pointer, Slice, UnsafePointer 일 때만 사용 가능, 그렇지 않으면 panic
func (v Value) Pointer() uintptr
// v 의 원본 값을 가리키는 unsafe.Pointer 반환
// v 의 Kind 가 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) Valuestring 을 예로 들기
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