reflect

編程語言中反射的概念

在計算機科學領域,反射是指一類應用,它們可以自描述和自控制。 也就是說,這類應用經過採用某種機制來實現對自已行爲的描述(self-representation)和監測(examination), 並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。java

每種語言的反射模型都不一樣,而且有些語言根本不支持反射。 golang語言實現了反射,反射機制就是在運行時動態的調用對象的方法和屬性,官方自帶的reflect包就是反射相關的,只要包含這個包就可使用。golang

多插一句,golang的gRPC也是經過反射實現的。編程

interface和反射

在講反射以前,先來看看golang關於類型設計的一些原則框架

  • 變量包括(type, value)兩部分
  • 理解這一點就知道爲何 nil != nil 了
  • type 包括static type和 concrete type. 簡單來講 static type是你在編碼是看見的類型(如int, string), concrete type是runtime系統看見的類型
  • 類型斷言可否成功,取決於變量的concrete type, 而不是static type. 所以, 一個reader變量若是它是concrete type也實現了write方法的話,它也能夠被類型斷言爲writer。

接下來要講的反射,就是創建在類型之上的,Golang的指定類型的變量的類型是靜態的(也就是指定int、string這些的變量,它的type是static type) 在建立變量的時候就已經肯定,反射主要與Golang的interface類型相關(它的type是concrete type),只有interface類型纔有反射一說。編程語言

在Golang的實現中,每一個interface變量都有一個對應pair,pair中記錄了實際變量的值和類型.函數

(value, type)

value是實際變量值,type是實際變量的類型。一個Interface{}類型的變量包含了2個指針,一個指針指向值的類型[對應concrete type],另一個指針指向實際的值[對應value] 例如,建立類型爲 *os.File的變量,而後將其賦值給一個接口變量r:性能

tty, err := os.OpenFile("/dev/tty",os.O_RDWR,0)
var r io.Reader
r = tty

接口變量r的pair中將記錄以下信息: (tty, *os.File),即便w是空接口類型,pair也是不變的。 interface及其pair是存在,是Golang中實現反射的前提,理解pair,就更容易理解反射。反射就是用來檢測存儲在接口變量內部(值value; 類型concrete type) pair以的一種機制。編碼

Golang的反射reflect

reflect的基本功能TypeOf和ValueOf

既然反射就是用來檢測存儲在接口變量內部(值value; 類型concrete type)pair對的一種機制。那麼在golang的reflect反射包中有什麼樣的方式可讓咱們直接獲取到變量內部的信息呢? 它提供了兩種類型(或者說兩個方法) 讓咱們能夠很容易的訪問接口變量內容,分別是reflect.ValueOf()和reflect.TypeOf(),看看官方的解釋:spa

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}

翻譯一下:ValueOf用來獲取輸入參數接口中的數據的值,若是接口爲空則返回0


// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}

翻譯一下:TypeOf用來動態獲取輸入參數接口中的值的類型,若是接口爲空則返回nil

reflect.Type()是獲取pair中的type,reflect.ValueOf()獲取pair中的value,示例以下:翻譯

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345
	fmt.Println("type: ", reflect.TypeOf(num))
	fmt.Println("value: ",reflect.ValueOf(num))
}

---
result:
type: float64
value: 1.2345

說明

  • 1.reflect.TypeOf: 直接給到了咱們想要的type類型,如float6四、int、各類pointer、struct等等真實的類型
  • 2.reflect.ValueOf: 直接給到了咱們想要的具體的值,如1.2345這個具體數值,或者相似&{1 "Allen.Wu" 25}這樣的結構體struct的值
  • 3.也就是說明反射能夠將"接口類型" 轉換爲 "反射類型對象",反射類型指的是reflect.Type和reflect.Value這兩種

從relfect.Value中獲取接口interface的信息

當執行reflect.ValueOf(interface)以後,就獲得一個類型爲"relfect.Value"變量, 能夠經過它自己的interface() 方法得到接口變量的真實內容 而後能夠經過類型判斷進行轉換,轉換爲原有真實類型。不過,咱們可有是已知原有類型,也有多是未知原有類型,所以,下面分兩種狀況進行說明。

已知原有類型[進行"強制轉換"]

已知類型後轉換爲其對應的類型的作法以下,直接經過interface方法而後強制轉換,以下:

realValue := value.Interface().(已知的類型)

示例以下:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345
	pointer := reflect.ValueOf(&num)
	value := reflect.ValueOf(num)
	
	// 能夠理解爲"強制轉換",可是須要注意的時候,轉換的時候,若是轉換的類型不徹底符合,則直接panic
	// golang 對類型要求很是嚴格, 類型必定要徹底符合
	// 以下兩個,一個是 *float64,一個是float64,若是弄混,則會panic
	convertPointer := pointer.Interface().(*float64)
	convertValue := value.Interface().(float64)
	
	fmt.Println(convertPointer)
	fmt.Println(convertValue)
}

---
result:
0xc42000e238
1.2345

說明

  • 1.轉換的時候,若是轉換的類型不徹底符合,則直接panic,類型要求很是嚴格!
  • 2.轉換的時候,要區分是指針仍是值
  • 3.也就是說反射能夠將"反射類型對象"再從新轉換爲"接口類型變量"

未知原有類型[遍歷探測其filed]

不少狀況下,咱們可能並不知道其具體類型,那麼這個時候,該如何作呢?須要咱們進行遍歷探測其Filed來得知,示例以下:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id int
	Name string
	Age int
}

func (u User) ReflectCallFunc() {
	fmt.Println("Allen.Wu ReflectCallFunc")
}

func main() {
	user := User{1, "Allen.Wu", 25}
	DoFiledAndMethod(user)
}

// 經過接口來獲取任意參數,而後一一揭曉
func DoFiledAndMethod(input interface{}) {
	getType := reflect.TypeOf(input)
	fmt.Println("get Type is :", getType.Name())
	
	getValue := reflect.ValueOf(input)
	fmt.Println("get all Fields is:",getValue)
	
	// 獲取方法字段
	// 1. 先獲取interface的reflect.Type,而後經過NumField進行遍歷
	// 2. 再經過reflect.Type的Field獲取其Field
	// 3. 最後經過Field的Interface()獲得對應的value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n",field.Name, field.Type, value)
	}
	
	// 獲取方法
	// 1.先獲取interface的reflect.Type, 而後經過.NumMethod進行遍歷
	for i := 0; i < getType.NumMethod(); i++ {
		m := getType.Method(i)
		fmt.Printf("%s: %v\n",m.Name, m.Type)
	}
}

---
result:
	get Type is : User
    get all Fields is: {1 Allen.Wu 25}
    Id: int = 1
    Name: string = Allen.Wu
    Age: int = 25
    ReflectCallFunc: func(main.User)
說明

經過運行結果能夠得知獲取未知類型的interface的具體變量及其類型的步驟爲:

  • 1.先獲取interface的reflect.Type,而後經過NumField進行遍歷
  • 2.再經過reflect.Type的Field獲取其Field
  • 3.最後經過Field的interface()獲得對應的value

經過運行結果能夠得知獲取未知類型的interface的所屬方法(函數)的步驟爲:

  • 1.先獲取interface的reflect.Type,而後經過NumMethod進行遍歷
  • 2.再分別經過reflect.Type的Method獲取對應的真實的方法(函數)
  • 3.最後對結果取其Name和Type得知具體的方法名
  • 4.也就是說反射能夠將"反射類型對象" 再從新轉換爲"接口類型變量"
  • 5.struct或者struct的嵌套都是同樣的判斷處理方式。

經過reflect.Value設置實際變量的值

reflect.Value是經過reflect.ValueOf(x)得到的,只有當X是指針的時候,才能夠經過reflect.Value修改實際變量X值,即: 要修改反射類型的對象就必定要保證其值是"addressable"的。 示例以下:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 1.2345
	fmt.Println("old value of pointer:", num)
	
	// 經過reflect.ValueOf獲取num中的reflect.Value,注意,參數必須是指針才能修改其值
	pointer := reflect.ValueOf(&num)
	newValue := pointer.Elem()
	
	fmt.Println("type of pointer:", newValue.Type())
	fmt.Println("settablility of pointer:", newValue.CanSet())
	
	
	// 從新賦值
	nweValue.SetFloat(77)
	fmt.Println("new value of pointer:", num)
	
	/*
	若是reflect.ValueOf的參數不是指針,會如何?
	pointer = reflect.ValueOf(num)
	newValue = pointer.Elem()
	若是非指針,這裏直接panic,
	"panic: reflect: call of reflect.Value.Elem on float64 Value"
	*/
}

------
result:
	old value of pointer: 1.2345
    type of pointer: float64
    settability of pointer: true
    new value of pointer: 77
說明
  • 1.須要傳入的參數是 *float64這個指針,而後能夠經過pointer.Elem()去獲取所指向的Value,注意必定要是指針。
  • 2.若是傳入的參數不是指針,而是變量,那麼 a.經過Elem獲取原始值對應的對象則直接panic b.經過CanSet方法查詢是否能夠設置返回false
  • 3.newValue.CantSet()表示是否能夠從新設置其值,若是輸出的是true則可修改,不然不能修改,修改完以後再進行打印發現真的已經修改了。
  • 4.reflect.Value.Elme()表示獲取原始值對應的反射對象,只有原始對象才能修改,當前反射對象是不能修改的
  • 5.也就是說若是要修改反射類型對象,其值必須是"addressable" 【對應的要傳入的是指針,同時要經過Elem方法獲取原始值對應的反射對象】
  • 6.struct或者struct的嵌套都是同樣的判斷處理方式

經過reflect.ValueOf來進行方法調用

這算是一個高級用法了,前面咱們只說到對類型、變量的幾種反射的用法,包括如何獲取其值、其類型、若是從新設置新值。 可是在工程應用中,另一個經常使用而且屬於高級的用法,就是經過reflect來進行方法函數的調用。 好比咱們要作框架工程的時候,須要能夠隨意擴展方法,或者說用戶能夠自定義方法,那麼咱們經過什麼手段讓用戶可以自定義呢? 關鍵點在於用戶的自定義方法是未可知的,所以咱們能夠經過reflect來搞定

示例以下:

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id int
	Name string
	Age int
}

func (u User) ReflectCallFuncHasArgs(name string, age int) {
	fmt.Println("ReflectCallFuncHasArgs name: ",name,", age:", age, "and origal User.Name:", u.Name)
}

func (u User) ReflectCallFuncNoArgs() {
	fmt.Println("ReflectCallFuncNoArgs")
}

// 如何經過反射來進行方法的調用?
// 原本能夠用u.ReflectCallFuncXXX直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是MethodByName,而後經過反射調動mv.Call

fucn main() {
	user := User{1, "Allen.Wu", 25}
	// 1.要經過反射來調用起對應的方法,必需要經過reflect.ValueOf(interface)來獲取到reflect.Value,獲得"反射類型對象"後才能作下一步處一
	getValue := reflect.ValueOf(user)
	
	// 必定要指定參數爲正確的方法名
	// 2.先看看帶有參數的調用方法
	methodValue := getValue.MethodByName("ReflectCallFuncHasArgs")
	args := []reflect.Value{reflect.ValueOf("wudebao"), reflect.ValueOf(30)}
	methodValue.Call(args)
	
	// 必定要指定參數爲正確的方法名
	// 3.再看看無參數的調用方法
	methodValue = getValue.MethodByName("ReflectCallFuncNoArgs")
	args = make([]reflect.Value, 0)
	methodValue.Call(args)
}

---
result:
	ReflectCallFuncHasArgs name:  wudebao , age: 30 and origal User.Name: Allen.Wu
    ReflectCallFuncNoArgs
說明
  • 1.要經過反射來調用起對應的方法,必需要先經過reflect.ValueOf(interface)來獲取到reflect.Value,獲得"反射類型對象"後才能作下一步處理
  • 2.reflect.Value.MethodByName,須要指定準確真實的方法名字,若是錯誤將直接panic,MethodByName返回一個函數值對應的reflect.Value方法的名字
  • 3.[]reflect.Value,這個是最終須要調用的方法的參數,能夠沒有或者一個或者多個,根據實際參數來定。
  • 4.reflect.Value的Call這個方法,這個方法最終調用真實的方法,參數務必保持一致,若是reflect.Value'Kind不是一個方法,那麼將直接panic。
  • 5.原本能夠用u.ReflectCallFuncXXX直接調用的,可是若是要經過反射,那麼首先要將方法註冊,也就是MethodByName,而後經過反射調用methodValue.Call

Golang的反射reflect性能

Golang的反射很慢,這個和它的API設計有關。在java裏面,咱們通常使用反射都是這樣來弄的。

Field field = clazz.getField("hello");
field.get(obj1);
field.get(obj2);

這個取得的反射對象類型是 java.lang.reflect.Field。它是能夠複用的。只要傳入不一樣的obj,就能夠取微電影這個obj上對應的field。 可是Golang的反射不是這樣設計的:

type, _ := reflect.TypeOf(obj)
field, _ := type_.FieldByName("hello")

這裏取出來的field對象是reflect.StructField類型,可是它沒有辦法用來取得對應對象上的值。若是要取值,得用另一套對object,而不是type的反射。

type, _ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")

這裏取出來的fieldValue類型是reflect.Value,它是一個具體的值,而不是一個可複用的反射對象上的值。若是要取值,得用另一套object,而不是type的反射。

type_ := reflect.ValueOf(obj)
fieldValue := type_.FieldByName("hello")

這裏取出來的fieldValue類型是reflect.Value,它是一個具體的值,而不是一個可複用的反射對象了,每次反射reflect.Value結構體,而且還涉及到GC。

小結

golang reflect慢主要有兩個緣由

  • 1.涉及到內存分配以及後續的GC;
  • 2.reflect實現裏面有大量的枚舉,也就是for循環,好比類型之類的。

總結

上述詳細說明了Golang的反射reflect的各類功能和用法,都附帶有相應的示例,相信可以在工程應用中進行相應實踐,總結一下就是:

  • 反射能夠大大提升程序的靈活性,使得interface{}有更大的發揮餘地 1.反射必須結合interface才玩得轉 2.變量的type要是concrete type的(也就是interface變量) 纔有反射一說

  • 反射能夠將"接口類型變量"轉換爲"反射類型對象" 1.反射使用TypeOf和ValueOf函數從接口中獲取目標對象信息

  • 反射能夠將"反射類型對象"轉換爲"接口類型變量" 1.reflect.value.Interface().(已知的類型) 2.遍歷reflect.Type的Field獲取其Field

  • 反射能夠修改反射類型對象,可是其值必須是"addressable" 1.想要利用反射修改對象狀態,前提是interface.data是 settable,即 pointer-interface

  • 經過反射能夠"動態"調用方法

  • 由於Golang自己不支持模板,所以在以往須要使用模板的場景下每每就須要使用反射(reflect)來實現.

相關文章
相關標籤/搜索