Golang接口(interface)三個特性(譯文)

The Laws of Reflection

原文地址html

第一次翻譯文章,請各路人士多多指教!golang

類型和接口

由於映射建設在類型的基礎之上,首先咱們對類型進行全新的介紹。
go是一個靜態性語言,每一個變量都有靜態的類型,所以每一個變量在編譯階段中有明確的變量類型,好比像:int、float3二、MyType。。。函數

好比:ui

type MyInt int
var i int
var j MyInt

變量i的類型爲int,變量j的類型爲MyInt,變量i、j具備肯定的類型,雖然i、j的潛在類型是同樣的,可是在沒有轉換的狀況下他們之間不能相互賦值。
在類型中有重要的一類爲接口類型(interface),接口類型爲一系列方法的集合。一個接口型變量能夠存儲接口方法中聲明的任何具體的值。像io.Reader和io.Writer是一個很好的例子,這兩個接口在io包中定義。翻譯

type Reader interface{
    Read(p []byte)(n int, err error)
}

type Writer interface{
    Writer(p []byte)(n int,er error)
}

任何聲明爲io.Reader或者io.Writer類型的變量均可以使用Read或者Writer 方法。也就意味着io.Reader類型的變量能夠賦值任何有Read方法的的變量。指針

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)

不管變量r被賦值什麼類型的值,變量r的類型依舊是io.Reader。go語言是靜態類型語言,而且r的類型永遠是io.Reader。
在接口類型中有一個重要的極端接口類型--空接口。
interface{}
他表明一個空的方法集合而且能夠被賦值爲任何值,由於任何一個變量都有0個或者多個方法。
有一種錯誤的說法是go的接口類型是動態定義的,其實在go中他們是靜態定義的,一個接口類型的變量老是有着相同類型的類型,儘管在運行過程當中存儲在接口類型變量的值具備不一樣的類型,可是接口類型的變量永遠是靜態的類型。code

接口的表示方法

關於go中接口類型的表示方法Russ Cox大神在一篇博客中已經詳細介紹[blog:http://research.swtch.com/2009/12/go-data-structures-interfaces.html]
一個接口類型的變量存儲一對信息:具體值,值的類型描述。更具體一點是,值是實現接口的底層具體數據項,類型是數據項類型的完整描述。htm

舉個例子:對象

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

變量r包含兩個數據項:值(tty),類型(os.File)。注意os.File實現的方法不只僅是Read,即便接口類型僅包含Read方法,可是值(tty)卻用於其完整的類型信息,所以咱們能夠按照以下方法調用blog

var w io.Writer
w = r.(io.Writer)

這條語句是一個斷言語句,斷言的意思是變量r中的數據項聲明爲io.Writer,由於咱們能夠將r賦值給w。執行完這條語句之後,變量w將和r同樣包含值(tty)、類型(*os.File)。即便具體值可能包含不少方法,可是接口的靜態類型決定什麼方法能夠經過接口型變量調用。

一樣咱們能夠

var empty interface{}
empty = w

這個接口型變量一樣包含一個數據對(tty,*os.File)。空接口能夠接受任何類型的變量,而且包含咱們可能用到的關於這個變量的全部信息。在這裏咱們不須要斷言是由於w變量知足於空接口。在上一個從Reader向Writer移動數據的例子中,咱們須要類型斷言,由於Reader接口中不包含Writer方法
切記接口的數據對中的內容只能來自於(value , concrete type)而不能是(value, interface type),也就是接口類型不能接受接口類型的變量。

1.從接口類型到映射對象

在最底層,映射是對存儲在接口內部數據對(值、類型)的解釋機制。首先咱們須要知道在reflect包中的兩種類型Type和Value,這兩種類型提供了對接口變量內部內容的訪問,同時reflect.TypeOf和reflect.ValueOf兩個方法檢索接口類型的變量。

首先咱們開始TypeOf

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var f float64 = 13.4
    fmt.Println(reflect.TypeOf(f))
    fmt.Println("Hello, playground")
}

結果

float64
Hello, playground

咱們能夠會感到奇怪這裏沒有接口呀?由於在程序中咱們能夠得知f的變量類型應爲float32,不該該是什麼變量類型。可是咱們在golang源碼中咱們得知,reflect.TypeOf包含一個空接口類型的變量.
func TypeOf(i interface{})Type
當咱們在調用reflect.TypeOf方法時,x首先存儲在一個空的接口中,而後再做爲一個參數傳送到reflect.TypeOf方法中,而後該方法解壓這個空的接口獲得類型信息。
一樣reflect.ValueOf方法,獲得值。

var f float64 = 13.4
    fmt.Println(reflect.ValueOf(f))

結果

13.4

reflect.Type和reflec.Value有許多方法讓咱們檢查和修改它們。一個比較重要的方法是Value有一個可以返回reflect.Value的類型的方法Type。另一個比較重要的是Type和Value都提供一個Kind方法,該方法可以返回存儲數據項的字長(Uini,Floatr64,Slice等等)。一樣Value方法也提供一些叫作Int、Float的方法讓咱們修改存儲在內部的值。

var f float64 = 13.44444
    v := reflect.ValueOf(f)
    fmt.Println(v)
    fmt.Println(v.Type())
    fmt.Println(v.Kind())
    fmt.Println(v.Float())

結果

13.444444444444445
float64
float64
13.444444444444445

同時有像SetInt、SetFloat之類的方法,可是咱們必須謹慎的使用它們。

反射機制有兩個重要的性質。首先,爲了保證接口的簡潔行,gettersetter兩個方法是能夠接受最大類型值的賦值,好比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.

第二個特性:接口保存了數據項底層類型,而不是靜態的類型,若是一個接口包含用戶定義的整數類型的值,好比

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

則v的Kind方法調用仍然返回的是reflect.Int,儘管x的靜態類型是MyInt。也能夠說,Kind`不會像Type`同樣將MyInt和int看成兩種類型來對待。

2.從映射對象到接口的值

像物理映射同樣,Go中的映射也有其自身的相反性。

經過利用Interface的方法咱們能夠將interface.Value恢復至接口類型,實際上這個方法將type和value信息包裝至interface類型而且返回該值。

// 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)

打印float64類型的值,實際上是接口類型變量v的映射。

或者咱們能夠這樣作,fmt.Println, fmt.Printf等函數的參數儘管是空的接口類型也能運行,在fmt包裏面解析出type和value的方法和咱們上面的例子類似。所以全部正確打印reflect.Value的方法都試經過interface的方法將值傳遞給格式化打印函數。

fmt.Println(v.Interface())

(爲何不是fmt.Println(v)?由於經過v是reflect.Value類型.)由於咱們的值底層是float64類型,所以咱們甚至能夠浮點類型的格式打印.

fmt.Printf("value is %7.1e\n", v.Interface())

結果是

3.4e+00

所以咱們不用類型斷言v.Interface{}到float64類型。由於接口類型內部保存着值的信息,Printf函數可以恢復這些信息。

簡單的說Interface是ValueOf的反操做,除非這個值老是靜態的Interface類型。

改變接口對象,他的值必須是可改變的

第三法則比較微妙而且容易混淆,可是若是從第一準則開始看的話,那麼仍是比較容易理解的。

這是一條錯誤的語句,可是這個錯誤值得咱們研究

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

若是你運行這條語句則會有下面的報錯信息

panic: reflect.Value.SetFloat using unaddressable value

由於變量v是不可更改的,因此提示值7.1是不可尋址的。可賦值是value的一個特性,可是並非因此的value都具備這個特性。

CanSet方法返回該值是不是能夠改變的,好比

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())

結果是

settability of v: false

若是在不能夠賦值的變量上進行賦值,就回引發錯誤。可是究竟是什麼纔是能夠賦值的呢?

可賦值的有點像是可尋址的,可是會更嚴格。映射對象能夠更改存儲值的特性能夠用來建立新的映射對象。映射對象包含原始的數據項是決定映射對象可賦值的關鍵。當下面代碼運行時

var x float64 = 3.4
v := reflect.ValueOf(x)

只是將x的拷貝到reflect.ValueOf,所以reflect.ValueOf的返回值是x的複製項,而不是x自己。假以下面這條語句能夠正常運行

v.SetFloat(5.4)

儘管v看起來是由x建立的,可是並不會更新x的值,由於這條語句會更新x拷貝值的值,可是並不影響x自己,所以可更改的這一特性就是爲了不這種操做。

雖然這看起來很古怪,但其實這是一種很熟悉的操做。好比咱們將x值賦值給一個方法

f(x)

咱們自己不想修改x的值,由於傳入的只是x值的拷貝,可是若是咱們想修改x的值,那麼咱們須要傳送x的地址(也就是x的指針)

f(&x)

這種操做是簡單明瞭的,其實對於映射也是同樣的。若是咱們想經過映射修改x的值,那麼咱們須要傳送x的指針。好比

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。爲了獲得指針的指向,咱們須要使用Elem()方法,該方法將會指向*p的值,而且將其保存到映射變量中

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())

結果爲

settability of v: true

如今v是一個可修改的映射對象。而且v表明x,所以咱們可使用v.SetFloat()來修改x的值。

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

輸出結果爲

7.1
7.1

映射是比較難理解的,儘管咱們經過映射的Values``Types隱藏了到底發生了什麼操做。咱們只須要記住若是想改變它的值,那在調用ValuesOf方法時應該使用指向它的指針。

Struct

在上一個例子中v並非指向自身的指針,而是經過其餘方式產生的。還有一種經常使用的操做就是修改結構體的某個字段,只要咱們知道告終構體的地址,咱們就能修改它的字段。

這有一個修改結構體變量t的例子。由於咱們要修改結構體的字段,因此咱們使用結構體指針建立結構體對象。咱們使用typeOfT表明t的數據類型,並經過NumField方法迭代結構體的字段。主意:咱們只是提取出結構體類型字段的的名字,而他們的reflect.Value對象。

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

值得注意的是只有可導出的字段才能使可修改的。

由於s包含一個可修改的映射對象,因此咱們能夠修改結構體的字段

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

結果爲

t is now {77 Sunset Strip}

若是s是經過t建立而不是&t,那麼SetInt和SetString方法都會出錯,由於t的字段是不能夠修改的。

原博客地址:The Go Blog|The Laws of Reflection

相關文章
相關標籤/搜索