天天看點

Go For Java Programmers(面向Java開發者的GO程式設計)

本文旨在幫助java開發者迅速掌握 go語言.

開始用一個很容易能被所有的java程式員認出的例子來突出特色,然後對go的架構給出了詳細的的描述,最後用一個例子來說明go結構中沒有與 java直接對應處。

<a href="http://www.nada.kth.se/~snilsson/go_for_java_programmers/src/collection/stack.go">stack.go</a>

頂級聲明出現之前,直接的評論是文檔注釋。他們是純文字。.

對于聲明,你把名字寫在類型後面.

<code>struct</code> 對應java中的類, 但struct組成不是方法而隻能是變量.

<code>tinterface{}</code>類型對應java的 <code>object</code>. 在go中它被所有的類型所實作,而不僅僅是引用類型.

代碼段 <code>(s *stack)</code> 聲明了一個方法,接收者 <code>s</code> 對應java中的 <code>this</code>.

操作符<code>:=</code>聲明并初始化了一個變量. 它的類型可以從初始化表達式中推導出.

這裡是一個的hello world程式,示範了如何使用<code>collection.stack</code>的抽象資料類型.

<a href="http://www.nada.kth.se/~snilsson/go_for_java_programmers/src/collection/example_test.go">example_test.go</a>

go的構造器沒有類。go 用 structs 和 interfaces來替代執行個體化方法,類的繼承機制,動态方法查找.也可用于java使用泛型接口

go提供所有類型的指針的值,而不隻是對象和數組。對于任何類型 t,有一個相應的指針類型*t表示指針指向類型 t的值。 offers pointers to values of all types, not just objects and arrays.

go允許任何類型都有方法而沒有裝箱的限制 allows methods on any type; no boxing is required. 方法receiver,在java中對應this可以是直接值或者是指針.

數組在go就是值. 當一個數組被當做函數的參數時,這個函數接收到的是數組的拷貝而不是它的指針. 然而在實踐中,函數經常使用 slices作為參數; slices引用了基礎數組.

該語言提供了字元串,一個字元串行為就像一個位元組片,但是是不可改變的。

該語言中的哈希表被稱作maps.

該語言提供了獨立運作的線程goroutines 和他們之間的通信管道channels.

某些類型(maps, slices, 和 channels)是按引用傳遞,而不是值。也就是說,傳遞一個map到函數并而不是拷貝map,如果函數修改了map,将被調用者看到變化。在java術語來說,可以認為這是一個map的引用.

go提供了兩種通路級别對應java的public和包的private.如果它的命名是大寫字母開頭就是最進階别public,反之就是包的private.

作為替換java中的異常機制, go采用了類型 error值來表示事件,如檔案結尾,和運作時的panics來表示運作時的錯誤,如數組越界等.

go不支援隐式類型轉換。混合使用不同類型的操作需要顯式轉換.

go不支援函數重載。在同一範圍内的函數和方法必須具有唯一的名稱.

go使用nil表示無效的指針,類似于java使用null.

聲明是跟java是相反的。你在類型後面再寫名稱,類型聲明從左往右更容易讀

go 與java相對應的

go

與java相對應的

<code>var v1 int</code>

<code>int v1;</code>

<code>var v2 *int</code>

<code>integer v2;</code>

<code>var v3 string</code>

<code>string v3 = "";</code>

<code>var v4 [10]int</code>

<code>int[] v4 = new int[10]; // v4 在go中是一個值.</code>

<code>var v5 []int</code>

<code>int[] v5;</code>

<code>var v6 *struct { a int }</code>

<code>c v6; // given: class c { int a; }</code>

<code>var v7 map[string]int</code>

<code>hashmap&lt;string,integer&gt; v7;</code>

<code>var v8 func(a int) int</code>

<code>f v8; // given: interface f { int f(int a); }</code>

聲明的一般形式是一個關鍵字後面跟着被聲明對象的名字.這個關鍵字是const, type, var, 或者func. 您也可以使用一個關鍵字,後面的括号中跟着一系列聲明.

當聲明一個函數,你必須提供每個參數的名稱,或者不提供任何參數的名稱,你不能提供了一些而忽略了另外一些名字。您可以組合幾個相同類型的名稱:

一個變量可以在聲明時初始化。當這樣做時,指定的變量的類型是允許的,但不是必需的。當未指定類型,預設的是初始化表達式的類型.

如果一個變量沒有立即初始化,必須要制定類型。那樣的情況下,它它會被隐式初始化該類型的零值zero value(0, nil, "", 等.). go不存在未初始化的變量.

在函數中,一個短的聲明句法是 := 表示.

這等效于

在go中,函數都是一等公民。go的函數類型表示一組所有具有相同的參數和傳回類型的函數.

go允許多重配置設定。在右邊的表達式會在評估後,再配置設定到任何的左操作數。

函數可以具有多個傳回值,表示由括号中的清單。傳回的值可以存儲配置設定給一個變??量清單。

空白辨別符提供了一種忽略多值表達式傳回值的方式,用下劃線字元表示: the blank identifier, represented by the underscore character, provides a way to ignore values returned by a multi-valued expression:

為了消除對分号和格式不必要的擔憂,你可能會用gofmt程式來寫go風格的标準代碼,雖然這種風格看起來很古怪,但熟悉了之後最終會像其他語言風格一樣變得舒服

go的代碼在實際中很多出現分号。嚴格來說,go所有的聲明都用分号結束。但是go毫無疑問會在每個非空白行的結尾插入一個分号,除非它還沒有完. 這樣做的後果是,在某些情況下,go不允許斷行。舉例,你可能會像下面這樣寫:

在g()後面會被插入一個分号,這樣就使他像是一個函數聲明而不是函數定義了類似的,你不能這樣寫:

在} 後和else前面會插入一個分号,導緻句法錯誤.

go在條件語句中并不使用括号,像 if條件語句 , for 條件語句的表達式, switch 條件語句的值. 另一方面,它并不需要在 if或 for條件語句中加花括号

此外, if和switch 接收一個可選的初始化的狀态,那麼慣用做法是建一個局部變量

go沒有while和do-while語句. 當for語句的條件比較單一時,他的作用就像是while語句. 完全省略條件則産生一個死循環。

for語句可能包含range周遊 strings, arrays, slices, maps, 或 channels。除了寫成下面這樣

去周遊a的元素,也可以寫成

這裡的i指索引, v代表的array, slice, 或者string的連續元素。對于字元串,i是一個位元組的索引,v指向rune類型(rune類型是int32)的一個别名)。maps疊代産生鍵 - 值對,而channels隻産生一個疊代值。

像java一樣,go許可break和continue指定一個标簽,但标簽上必須指的for, switch, 或者 select 語句.

在 switch 語句,case 标簽預設情況下不通過,但你可以讓他們 fallthrough語句結束的情況下通過了。

但是一個case可以包含過個值

case的值可以支援任何類型的相等比較操作符,如字元串或指針。一個丢失表達式的switch語句 等價于表達式為 true。

++和--隻能作為字尾操作符,和僅在語句中,而不是在表達式中。例如,你不可以寫n = i++。

defer語句調用一個函數的執行被推遲到函數傳回那一刻。defer語句執行時,被遞延的函數的參數被計算,并儲存,以備将來使用

go中的常數可能是untyped的。這适用于無類型的常量表達式的數字文本,和使用const聲明的無類型的常量表達式。當它被用在需要一個帶類型的值的背景下,一個無類型的常量的可以被轉變成有類型的值。這樣常量的使用相對自由,即使go沒有隐式類型轉換

語言對非類型化的數字常量不限制大小。限制僅适用于使用一個常量時,其中一種類型是必需的。

如果是不存在的變量聲明的類型和相應的表達式的計算結果為一個非類型化的數字常數,這個常數是被轉換為 rune, int, float64, 或者complex128 類型,取決于該值是否一個字元,整數,浮點,或複雜的常數。

go 不存在枚舉類型。相反,你可以使用特殊的名字iota在單一的const聲明中進而得到一系列累加值。當初始化表達式省略為一個 const,它重用了前面的表達式。

結構體對應于java中的類,但一個結構的成員不能是方法,而是變量。結構體的指針是類似java的的引用變量。與java類不同的是,結構也可以被定義為直接值。在這兩種情況下使用 .來通路結構體的成員。

在go中,方法可以與任何命名的類型關聯,而不僅僅是與結構體。詳情見方法和接口的讨論。

如果你有一個int或struct或者array需要配置設定對象的内容複制。 想達到java的引用變量的效果,go使用指針。對于任何類型e t,有一個相應的指針類型*t,表示指針類型 t的值

給指針變量配置設定存儲空間,使用内置函數new,傳入一個類型,并傳回一個指針,指向配置設定的存儲空間。配置設定的空間将被零初始化的類型。例如, new(int) 配置設定存儲為一個新的int,初始化它的值為e 0,并傳回它的位址,類型 *int。

java代碼t p = new t(),其中 t是一個兩個int型執行個體變量a和b的類,對應于

或者習慣性這樣幹

var v t代表聲明,聲明了一個變量包含一個值類型 t,這在java中是沒有的。也可使用複合方式建立并初始化值。

等同于

對于類型t的操作數x,位址運算符 &amp;x提供值類型為*t的x的位址,

slice是概念上一個結構包含三個域:一個數組的指針、長度和容量。切片支援[]操作符來通路底層數組的元素。内置的len函數傳回的切片長度。内置的的cap函數傳回切片的能力。

給定一個數組,或另一個切片,通過a[i:j]來建立一個新的切片。這個新建立的切片指向a,從索引i開始,并結束索引j之前。它的長度是j - i。如果i 被省略,切片從0開始。如果j 被省略,切片在 len(a)結束。新切片跟 a一樣指向相同的數組。即,改變後組成的新的切片的元素在a都能見到。新切片的容量就是簡單的a減去i。數組的容量就是數組的長度。

如果你建立一個值類型為[100]byte(100個位元組,也許是一個緩沖區的數組),你想不複制它,而将它傳遞給函數,那麼函數的參數聲明類型[]byte,并傳入數組的切片。切片也可以用 make的函數建立(如下文所述)。

切片組合采用内置的append函數,java的arraylist提供相同的功能。

切片文法,也可以使用在字元串上。它傳回一個新字元串,其值是原始的字元串的子串

map and channel values must be allocated using the built-in function make. for example, calling map和channel值必須使用内置的函數make。例如,調用

make(map[string]int) map[string]int傳回一個新配置設定的值類型。相對于new, make 傳回的是實際的對象,而不是一個位址。這是一緻的事實,map和channel是引用類型。

對于map,make函數将容量作為一個可選的第二個參數的提示。對于channel,有一個可選的第二個參數來設定channel的緩沖能力,預設為0(無緩沖)。

make函數也可以用來配置設定一個切片。在這種情況下,它配置設定記憶體給基本數組并傳回一個引用他的切片。該切片中的元素數是一個必需的參數。第二個可選的參數是切片的容量。

方法看起來像一個普通的函數定義,但它有一個receiver(接收者)。receiver是類似java執行個體方法中的this引用。

這聲明了一個方法get與mytype關聯的。receiver被命名為p 在函數體内。

命名的類型來定義方法。如果您轉換不同類型的值,新的值将有新的類型,而不是那些舊的類型。

你可以定義一個内置類型的方法,用新的命名類型聲明。新的類型和内置的類型是不同的。

go接口類似于java接口,但可被視為一個實作該接口提供任何類型的在go接口命名的方法。明确的聲明是不必要的。

接口像這樣:

自從 mytype 已經有了get 方法, 我們可以讓 mytype滿足接口通過添加

現在任何隻要将myinterface當做參數就可以接收類型是*mytype的變量

在java術語,給mytype 定義 set和get 使mytype自動實作了myinterface接口。這種類型型可滿足多個接口。這是一種形式的鴨子類型。

“當看到一隻鳥走起來像鴨子、遊泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。” james whitcomb riley

匿名域可以用于實作很像一個java子類的東西。

set方法是繼承自mytype的,因為關聯了匿名域的方法的變為了封閉類型的方法。在這種情況下,因為 mysubtype有一個匿名與域 mytype類型,是以為 mytypee的方法也成為mysubtype的方法。get方法被重寫,set方法被繼承。

這是與java中的子類不完全相同。當一個匿名域的方法被調用時,它的 receiver就是這個匿名域,而不是周圍的結構體。換句話說,匿名域上的方法的不會動态排程。當你想要實作相當于java的動态方法查找,請使用接口。

使用一個類型斷言可以使具有一個接口類型的變量轉換成具有不同的接口類型。這是在運作時動态執行。與java不同,并不需要任何聲明兩個接口之間的關系。

轉換為printer 完全是動态的。隻要x(x中存儲的值的實際類型)的 動态類型 定義了一個print方法。

go的多值的傳回可以很容易地沿着正常的傳回值傳回詳細的錯誤消息。按照慣例,這樣的error類型的消息,就像下面的簡單的内置接口

例如,os.open函數當它無法打開檔案時傳回一個非零error值。

以下代碼使用 os.open打開一個檔案。如果 error發生,它調用 log.fatal列印錯誤資訊并停止。

error的接口隻需要一個 error的方法,但具體的error的實作往往有額外的方法,允許調用者進行檢查錯誤的詳細資訊

panic(恐慌)是一個運作時錯誤,展開goroutine的堆棧,一路運作任何遞延的功能,然後停止程式。恐慌與java異常相似,但隻适用于運作時的錯誤,如一個nil 指針或試圖索引數組越界。 go程式使用内置的error類型 (詳見上文)為了表示如檔案結束等事件。

可以使用内置的recover (恢複),重新獲得控制權的恐慌的goroutine和恢複正常運作。呼叫recover停止展開,并傳回傳入的參數panic。因為隻有運作的未展開代碼,内部含有延遲函數,recover隻在内遞延的函數有效。如果的goroutine是沒有恐慌,recover傳回nil。

go允許用go開啟一個新的執行線程--goroutine。它運作在不同的,新建立的的goroutine中。在一個程式中的所有goroutine共享相同的位址空間。

goroutines是輕量級的,隻占用比堆棧配置設定多一點的空間。堆棧開始小和成長的配置設定和釋放堆(heap)的要求。内部goroutines像進行了複用多個作業系統線程的協程。您不必擔心這些細節。

go處理文字的函數,可以作為結束,在處理go時很強大

變量text和delay在周圍函數和函數文字之間共享;隻要它們都可以通路,它們就存在。

管道通過指定的元素類型的值來提供兩個goroutine同步執行和溝通的機制。 &lt;- 操作符指定通道的方向,發送或接收。如無任何訓示方向時,通道是雙向的。

管道是一個引用類型,用make配置設定。

to receive a value on a channel, use it as a unary operator. 使用&lt;- 作為一個二進制操作符來在管道上發送值。當在管道上接收一個值時,把它作為一進制運算符。

如果管道是無緩沖,那麼發送者阻塞,直到接收器接收到值。如果管道有一個緩沖區,發送者阻塞,直到該值已被複制到緩沖區。如果緩沖區已滿,這意味着等待,直到一些接收器中檢索到值。接收器被阻塞,直到有資料接收。

最後我們用一個例子來說明如何散落的内容拼起來。這是一個伺服器通過管道來接受的work請求的例子。每個請求都在一個單獨的goroutine運作。 work 結構本身包含了一個管道,用于傳回一個結果。