如果某個函數的入參是interface{},有下面幾種方式可以擷取入參的方法:
1 fmt:
import "fmt"func main() { v := "hello world" fmt.Println(typeof(v))}func typeof(v interface{}) string { return fmt.Sprintf("%T", v)}
2 反射:
import ( "reflect" "fmt")func main() { v := "hello world" fmt.Println(typeof(v))}func typeof(v interface{}) string { return reflect.TypeOf(v).String()}
3 類型斷言:
func main() { v := "hello world" fmt.Println(typeof(v))}func typeof(v interface{}) string { switch t := v.(type) { case int: return "int" case float64: return "float64" //... etc default: _ = t return "unknown" }}
其實前兩個都是用了反射,fmt.Printf(“%T”)裡最終調用的還是reflect.TypeOf()
。
func (p *pp) printArg(arg interface{}, verb rune) { ... // Special processing considerations. // %T (the value's type) and %p (its address) are special; we always do them first. switch verb { case 'T': p.fmt.fmt_s(reflect.TypeOf(arg).String()) return case 'p': p.fmtPointer(reflect.ValueOf(arg), 'p') return }
reflect.TypeOf()的參數是v interface{}
,golang的反射是怎麼做到的呢?
在golang中,interface也是一個結構體,記錄了2個指標:
- 指標1,指向該變數的類型
- 指標2,指向該變數的value
如下,空介面的結構體就是上述2個指標,第一個指標的類型是type rtype struct
;非空介面由於需要攜帶的資訊更多(例如該介面實現了哪些方法),所以第一個指標的類型是itab,在itab中記錄了該變數的動態類型: typ *rtype
。
// emptyInterface is the header for an interface{} value.type emptyInterface struct { typ *rtype word unsafe.Pointer}// nonEmptyInterface is the header for a interface value with methods.type nonEmptyInterface struct { // see ../runtime/iface.go:/Itab itab *struct { ityp *rtype // static interface type typ *rtype // dynamic concrete type link unsafe.Pointer bad int32 unused int32 fun [100000]unsafe.Pointer // method table } word unsafe.Pointer}
我們來看看reflect.TypeOf():
// 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 { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ)}
TypeOf看到的是空介面interface{},它將變數的地址轉換為空白介面,然後將將得到的rtype轉為Type介面返回。需要注意,當調用reflect.TypeOf的之前,已經發生了一次隱式的類型轉換,即將具體類型的向空介面轉換。這個過程比較簡單,只要拷貝typ *rtype
和word unsafe.Pointer
就可以了。
例如w := os.Stdout
,該變數的介面值在記憶體裡是這樣的:
那麼對於第三種,類型斷言是怎麼判斷是不是某個介面呢?回到最初,在golang中,介面是一個松耦合的概念,一個類型是不是實現了某個介面,就是看該類型是否實現了該介面要求的所有函數,所以,類型斷言判斷的方法就是檢查該類型是否實現了介面要求的所有函數。
走讀k8s代碼的時候,可以看到比較多的類型斷言的用法:
func LeastRequestedPriorityMap(pod *api.Pod, meta interface{}, nodeInfo *schedulercache.NodeInfo) (schedulerapi.HostPriority, error) { var nonZeroRequest *schedulercache.Resource if priorityMeta, ok := meta.(*priorityMetadata); ok { nonZeroRequest = priorityMeta.nonZeroRequest } else { // We couldn't parse metadata - fallback to computing it. nonZeroRequest = getNonZeroRequests(pod) } return calculateUnusedPriority(pod, nonZeroRequest, nodeInfo)}
類型斷言的實現在src/runtime/iface.go裡(?),不過這塊代碼沒看懂,等以後再更新吧。
func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) { tab := i.tab if tab == nil { return } if tab.inter != inter { tab = getitab(inter, tab._type, true) if tab == nil { return } } r.tab = tab r.data = i.data b = true return}func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) { t := e._type if t == nil { return } tab := getitab(inter, t, true) if tab == nil { return } r.tab = tab r.data = e.data b = true return}
查看原文地址