>  기사  >  백엔드 개발  >  Golang의 리플렉션을 자세히 설명하는 기사

Golang의 리플렉션을 자세히 설명하는 기사

青灯夜游
青灯夜游앞으로
2022-12-14 20:26:346111검색

이 글에서는 당신에 대한 새로운 이해를 얻기 위해 GolangMedium Reflection에 대해 주로 이야기할 것입니다.

Golang의 리플렉션을 자세히 설명하는 기사

많은 사람들이 Go 언어를 일정 기간 사용해왔고, 심지어 1~2년 정도 사용한 사람도 있지만, 여전히 Go 언어에 대한 성찰에 대해 모호하고 자신감이 별로 없습니다. 그것을 사용할 때. [관련 권장사항: Go 비디오 튜토리얼, 프로그래밍 교육]

게다가 반사도 거의 사용하지 않습니다. 물론 직장에서 사용하는 것이 가장 간단하고 효율적입니다. 확장 가능하고 성능이 좋습니다. 일부 고급 사용법을 기계적으로 복사할 필요가 없습니다. 우리는 많은 것을 실험할 수 있습니다. 이번에는 성찰 놀이 방법에 대해 자세히 살펴보겠습니다

기사

  • 다음 다섯 가지 측면에서 성찰이란 무엇입니까?
  • 성찰의 규칙
  • 사용 사례 및 유연한 적용
  • Reflection 원리
  • Summary

간단히 말하면, Reflection은

간단히 말해서, Reflection은 프로그램이 실행되는 동안 프로그램 자체에 액세스하고 수정하는 기능입니다. 예를 들어 프로그램이 실행 중일 때 프로그램의 필드 이름과 필드 값을 수정할 수 있으며, 프로그램 정보 등에 대한 인터페이스 액세스를 제공할 수도 있습니다.

이것은 Go 언어에서 제공되는 메커니즘입니다. . Go 언어 공용 라이브러리에서 Reflect의 사용에 대해 많이 볼 수 있습니다.

예를 들어 일반적으로 사용되는 fmt 패키지, commonly Used json 시퀀스 물론 앞서 언급한 gorm 라이브러리에서도 Reflection을 사용합니다. 그런데 왜 우리는 일반적으로 리플렉션을 사용합니까?

반사 능력을 바탕으로 당연히 우리가 제공하는 인터페이스는 들어오는 데이터 유형이 무엇인지 모르기 때문에 특정 데이터 유형은 프로그램이 실행될 때만 알 수 있습니다

하지만 코딩할 때 예상합니다. 프로그램이 실행될 때 전달된 유형(예: json 직렬화)과 이 특정 데이터에 대해 작업을 수행하려면 이때 리플렉션 기능을 사용해야 합니다

그래서 사용하려면

인터페이스를 볼 수 있다는 것이 놀랍지 않나요? {}

어디에 반영되나요? 들어오는 데이터 유형이 무엇인지 확실하지 않기 때문에 이를 인터페이스로 설계했습니다{}. 인터페이스의 특성과 사용법에 대해 잘 모르는 경우 다음 기사를 확인하세요.

인터페이스{}에 관해 어떤 점에 주의해야 합니까?

인터페이스{}에 관해 어떤 점에 주의해야 합니까? 다음으로

  • 먼저 반성의 규칙에 주의하세요

Reflection은 인터페이스 유형 변수를 반사 유형 객체로 변환할 수 있습니다

Reflection은 반사 유형 객체를 인터페이스 유형 변수로 변환할 수 있습니다

We 만약 당신이 런타임에 리플렉션 유형 개체를 수정하려면 이 개체에 해당하는 값을 쓸 수 있어야 합니다. 위의 세 가지 규칙도 비교적 이해하기 쉽습니다.

우리는 일반적으로 사용하는 데이터 유형을 패키지(예: 안전하지 않은 패키지 또는 반영 패키지)의 지정된 데이터 유형으로 변환한 다음 패키지의 규칙에 따라 데이터를 수정합니다
  • 조끼를 바꾸는 것과 동일하며 다음과 같은 작업을 수행할 수 있습니다. do it 다양한 조작

  • 사용 사례에 주의하고 유연하게 사용하세요
  • 일반적으로 먼저 기본 응용 프로그램을 이해한 다음 그 원리와 왜 이런 식으로 사용될 수 있는지 연구하고 천천히 이해하게 됩니다. 더 깊이

법칙 1의 경우 인터페이스 유형 변수를 반사 유형 객체로 변환합니다

실제로 여기에 언급된 인터페이스 유형 변수의 경우

etc.

와 같은 모든 데이터 유형의 변수를 전달할 수 있습니다.

Reflection 유형 객체는 여기서 제공되는 reflect.Type reflect.Value 对象,可以通过 reflect 包中提供的 TypeOfValueOf 函数得到

其中 reflect.Type 实际上是一个 interface ,他里面包含了各种接口需要进行实现,它里面提供了关于类型相关的信息

其中如下图可以查看到 reflect.Type 的所有方法,其中

  • 绿色的 是所有数据类型都是可以调用的
  • 红色的是 函数类型数据可以调用的
  • 黑色的是 Map,数组 Array,通道 Chan,指针 Ptr 或者 切片Slice 可以调用的
  • 蓝色的是结构体调用的
  • 黄色的是通道 channel 类型调用的

reflect.Value 实际上是一个 struct,根据这个 struct 还关联了一组方法,这里面存放了数据类型和具体的数据,通过查看其数据结构就可以看出

type Value struct {
   typ *rtype
   ptr unsafe.Pointer
   flag
}

看到此处的 unsafe.Pointer 是不是很熟悉,底层自然就可以将 unsafe.Pointer 转换成 uintptr,然后再修改其数据后,再转换回来,对于 Go 指针不太熟悉的可以查看这篇文章:

写一个简单的 demo 就可以简单的获取到变量的数据类型和值

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))
}

对于定律二,将 反射类型的对象 转换成 接口类型的变量

我们可以通过将 reflect.Value 类型转换成我们具体的数据类型,因为 reflect.Value 中有对应的 typ *rtype 以及 ptr unsafe.Pointer

例如我们可以 通过 reflect.Value 对象的 interface() 方法来处理

func main() {   var demoStr string = "now reflect"
   fmt.Println("type:", reflect.TypeOf(demoStr))
   fmt.Println("value:", reflect.ValueOf(demoStr))   var res string
   res = reflect.ValueOf(demoStr).Interface().(string)
   fmt.Println("res == ",res)
}

对于定律三,修改反射类型的对象

首先我们看上书的 demo 代码,传入 TypeOfValueOf 的变量实际上也是一个拷贝,那么如果期望在反射类型的对象中修改其值,那么就需要拿到具体变量的地址然后再进行修改,前提是这个变量是可写的

举个例子你就能明白

func main() {
   var demoStr string = "now reflect"
   v := reflect.ValueOf(demoStr)
   fmt.Println("is canset ", v.CanSet())
   //v.SetString("hello world")   // 会panic
   }

可以先调用 reflect.Value 对象的 CanSet 查看是否可写,如果是可写的,我们再写,如果不可写就不要写了,否则会 panic

那么传入变量的地址就可以修改了??

传入地址的思路没有毛病,但是我们去设置值的方式有问题,因此也会出现上述的 panic 情况

此处仔细看能够明白,反射的对象 v 自然是不可修改的,我们应该找到 reflect.Value 里面具体具体的数据指针,那么才是可以修改的,可以使用 reflect.Value TypeOf를 통해 액세스할 수 있는 Reflect 패키지의 reflect.Type Reflect.Value 객체로 이해될 수 있습니다. Reflect 패키지에서 ValueOf

함수는

여기서 reflect.Type 은 실제로 구현해야 하는 다양한 인터페이스가 포함된 인터페이스이며 볼 수 있는 유형

에 대한 정보를 제공합니다. 아래와 같이 reflect.Type의 모든 메소드에

green

은 모든 데이터 타입을 호출할 수 있으며
    red
  • 함수 타입 데이터를 호출할 수 있습니다 검은색은 Map, array Array, 채널 Chan, 포인터 Ptr 또는 슬라이스라고 할 수 있는 슬라이스

파란색🎜은 구조에 의해 호출되고 🎜🎜🎜🎜🎜노란색🎜은 채널에 의해 호출됩니다. 🎜🎜🎜🎜🎜 Reflect.Value 는 실제로 구조체입니다. 이 구조체에 따르면 데이터 유형과 특정 데이터를 저장하는 메서드 집합도 연결되어 있습니다. 데이터 구조를 보면 알 수 있습니다🎜
type RDemo struct {
   Name  string
   Age   int
   Money float32
   Hobby map[string][]string
}

func main() {
   tmp := &RDemo{
      Name:  "xiaomiong",
      Age:   18,
      Money: 25.6,
      Hobby: map[string][]string{
         "sport": {"basketball", "football"},
         "food":  {"beef"},
      },
   }

   v := reflect.ValueOf(tmp).Elem()  // 拿到结构体对象
   h := v.FieldByName("Hobby")    // 拿到 Hobby 对象
   h1 := h.MapKeys()[0]    // 拿到 Hobby 的第 0 个key
   fmt.Println("key1 name == ",h1.Interface().(string))

   sli := h.MapIndex(h1)    // 拿到 Hobby 的第 0 个key对应的对象
   str := sli.Index(1)      // 拿到切片的第 1 个对象
   fmt.Println(str.CanSet())

   str.SetString("helloworld")
   fmt.Println("tmp == ",tmp)
}
🎜여기를 참조하세요. unsafe.Pointer에 익숙하십니까? 맨 아래 레이어는 자연스럽게 unsafe.Pointeruintptr로 변환한 다음 해당 데이터를 수정하고 다시 변환할 수 있습니다. not 너무 익숙하다면 다음 기사를 확인해 보세요: 🎜🎜🎜GO의 포인터? 🎜🎜🎜변수의 데이터 유형과 값을 쉽게 얻을 수 있는 간단한 데모를 작성하세요🎜
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}
🎜🎜

🎜법칙 2의 경우 반사 유형 객체를 인터페이스 유형 변수로 변환🎜🎜 🎜우리는 reflect.Value 유형을 특정 데이터 유형에 입력하세요. reflect.Value ptr unsafe에 해당 <code>typ *rtype이 있기 때문입니다. 포인터🎜🎜예를 들어 reflect.Value 객체🎜🎜🎜
// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}复制代码
🎜🎜

법칙 3의 경우 반사 유형 객체를 수정하세요🎜🎜먼저 살펴보겠습니다 책의 데모 코드에서 🎜TypeOf🎜 및 🎜ValueOf🎜에 전달된 변수는 실제로 복사본이므로 리플렉션 유형 개체에서 해당 값을 수정하려면 특정 변수의 주소를 가져온 다음 수정해야 합니다. 🎜이 변수는 쓰기 가능하다는 전제가 있습니다🎜🎜🎜예제를 보면 알 수 있습니다🎜rrreee🎜🎜🎜먼저 reflect.Value 객체의 CanSet를 호출하여 쓰기 가능한지 확인할 수 있습니다. 즉, 다시 쓰자. 쓸 수 없으면 쓰지 마세요. 그렇지 않으면 패닉 상태가 됩니다🎜🎜🎜🎜그럼 전달된 변수의 주소를 수정할 수 있다는 건가요? ? 🎜🎜🎜🎜들어오는 주소가 있습니다 아이디어에는 문제가 없지만 값을 설정하는 방식에 문제가 있으므로 위에서 언급한 패닉 상황도 발생하게 됩니다. 여기서 잘 살펴보면 반영된 객체 v가 당연히 수정 불가능하다는 것을 알 수 있습니다. reflect.Value 의 특정 데이터 포인터는 약간 더 복잡한 경우 reflect.Value 의 🎜Elem🎜 메서드를 사용할 수 있습니다. , 이렇게 단순한 경우라고 생각하실 수도 있고, 시연만 하면 괜찮겠지만, 직장에서 사용하면 바로 크래쉬가 납니다. 당연히 아직 완전히 이해되지는 않았습니다. 잘 소화되지 않았습니다. 작업에 대한 또 다른 예가 있습니다🎜🎜🎜구조에는 맵이 있고 맵의 키는 문자열이며 값은 []string🎜🎜🎜🎜입니다. 요구 사항은 🎜1🎜번째에 액세스하는 것입니다. 맵 키 🎜sport🎜를 사용하여 구조에서 취미 분야에 해당하는 슬라이스 요소를 추가하고 🎜 hellolworld🎜🎜🎜rrreee🎜🎜🎜로 수정합니다.

可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点

看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢

有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式

反射原理

那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic

TypeOf

来看 TypeOf 的接口中涉及的数据结构

在 reflect 包中 rtype 是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
   size       uintptr
   ptrdata    uintptr
   hash       uint32 
   tflag      tflag
   align      uint8
   fieldAlign uint8
   kind       uint8
   equal     func(unsafe.Pointer, unsafe.Pointer) bool
   gcdata    *byte 
   str       nameOff
   ptrToThis typeOff
}

其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容

ValueOf

ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构

// emptyInterface is the header for an interface{} value.type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}复制代码

emptyInterface 结构中有 rtype 类型的指针, word 自然是对应的数据的地址了

reflect.Value 对象中的方法也是非常的多,用起来和上述说到的 reflect.Type 接口中的功能类似

关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好

殊不知,此处的 reflect.Value 也是可以转换成 reflect.Type ,可以查看源码中 reflect\value.gofunc (v Value) Type() Type {

其中   reflect.Value  ,reflect.Type ,和任意数据类型 可以相互这样来转换

如下图:

总结

至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点

关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的

高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧

大道至简,反射三定律,活学活用

更多编程相关知识,请访问:编程视频!!

위 내용은 Golang의 리플렉션을 자세히 설명하는 기사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제