Go包匯入與Java的差別
閒暇時翻閱了近期下載到的電子書《Go in Practice》 ,看到1.2.4 Package Management一節中的程式碼Demo,感覺作者對Go package匯入的說法似乎不夠精確:“Packages are imported by their name”(後續的說明將解釋不精確的原因)。聯想到前幾天遇到的一個Java包匯入的問題,讓我隱約地感覺Java程式設計師很容易將兩種語言的Package import機制搞混淆,於是打算在這裡將Golang和Java的Package import機制做一個對比,對於Java轉型到Golang的程式設計師將大有裨益:)。這裡的重點在於與Java的對比,關於Golang的Package Import的細節可以參考我之前寫過的一篇文章
我們先來看兩個功能等價的程式碼。
//TestDate.java import java.util.*; import java.text.DateFormat; public class TestDate { public static void main(String []args){ Date d = new Date(); String s = DateFormat.getDateInstance().format(d); System.out.println(s); } }
和
//testdate.go
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
fmt.Println(t.Format("2006-01-02"))
}
兩個程式在Run時,都輸出下面內容:
2016-9-13
我們看到Golang和Java都是用import關鍵字來進行包匯入的:
import java.util.Date;
Date d = new Date();
vs.
import "time"
t := time.Now()
咋看起來,Java在package import後似乎使用起來更Easy,使用包內的類和方法時,前面無需再附著Package name,即Date d,而不是java.util.Date d。而Go在匯入”time”後,引用包中方法時依然要附著著包名,比如time.Now()。但實質上兩種語言在import package的機制上是有很大不同的。
1、機制
雖然都使用import,但import關鍵字後面的字串所代表的含義有不同。
Java import匯入的是類而不是包,import後面的字串表示的是按需匯入Java Package下面的類,比如import java.util.*; 或匯入Package下某個類,比如import java.util.Date。而Go import關鍵字後面的字串是包名嗎?很多初學者會認為這個就是Go包名,實則不然,Go import後面的字串實際上是一個包匯入路徑,這也是Java用”xxx.yyy.zzz”形式而Golang使用”xxx/yyy/zzz”形式的原因。我們用個簡單的例子就能證明這一點。我們知道Golang會在\$GOROOT/src + \$GOPATH/src下面匯入xxx/yyy/zzz路徑下的包,我們在import “fmt”時,實際上匯入的是\$GOROOT/src/fmt目錄下的包,只是恰好這個下面的包的名字是fmt罷了。如果我們將\$GOROOT/src/fmt目錄改名為fmt1,結果會是如何呢?
$go build helloworld.go
helloworld.go:3:8: cannot find package "fmt" in any of:
/Users/tony/.bin/go17/src/fmt (from $GOROOT)
/Users/tony/Test/GoToolsProjects/src/fmt (from $GOPATH)
helloworld.go是一個helloworld go原始碼。
之所以出錯是因為在\$GOROOT/src下已經沒有fmt這個目錄了,所以下面程式碼中的兩個fmt含義是不同的(這也解釋了Go in practice中關於包匯入的說法的不精確的原因):
package main
import "fmt" ---- 這裡的fmt指的是$GOROOT/src下的名為"fmt"的目錄名
func main() {
fmt.Println("Hello, World") --- 這裡的fmt是真正的包名"fmt"
}
從上面我們可以看出Go的包名和包的原始檔所在的路徑的名字並沒有必須一致的要求,這也是為什麼在Go原始碼使用包時一定是用packagename.XX形式,而不是packagename.subpackagename.XX的形式了。比如匯入”net/http”後,我們在原始碼中使用的是http.xxx,而不是net.http.xxx,因為net/http只是一個路徑,並不是一個巢狀的包名。
之所以看起來匯入路徑的終段目錄名與包名一致,只是因為這是Go官方的建議:Go的匯入路徑的最後一段目錄名(xxx/yyy/zzz中的zzz)與該目錄(zzz)下面原始檔中的Go Package名字相同。
下面是一個非標準庫的包名與匯入路徑終段名完全不一致的例子:
//github.com/pkgtest/pkg1/foo.go
package foo
import "fmt"
func Foo() {
fmt.Println("Foo in pkg1")
}
//testfoo.go
package main
import (
"github.com/pkgtest/pkg1"
)
func main() {
foo.Foo() //輸出:Foo in pkg1
}
可以看出testfoo.go匯入的是”github.com/pkgtest/pkg1″這個路徑,但這個路徑下的包名卻是foo。
Java語言中的包實際以.jar為單位,.jar內部實際上也是以路徑組織.class檔案的,比如:foo.jar這個jar包中有一個package名為:com.tonybai.foo,foo包中包含類Foo、Bar,那實際上foo.jar內部的目錄格式將是:
foo.jar
- com/
- tonybai/
- foo/
- Foo.class
- Bar.class
但對於Java包的使用者,這些都是透明的。
2、重名
Java中關於包匯入(實則是類匯入)唯一的約束就是不能有兩個類匯入後的full name相同,如果存在兩個匯入類的full name完全相同,Javac在resolve時,要以ClassPath路徑的先後順序為準了,選擇最先遇到的那個類。但是在Go中,如果匯入的兩個路徑下的包名相同,那麼Go compiler顯然是不能允許這種情況的存在的,會給出Error資訊。
比如我們在GOPATH下的github.com/pkgtest/pkg1和github.com/pkgtest/pkg2下放置了同名包foo,下面程式碼將會報錯:
package main
import (
"github.com/pkgtest/pkg1"
"github.com/pkgtest/pkg2"
)
func main() {
foo.Foo()
}
錯誤資訊如下:
$go run testfoo.go
# command-line-arguments
./testdate.go:8: foo redeclared as imported package name
previous declaration at ./testfoo.go:7
解決這一問題的方法就是採用package alias:
package main
import (
a "github.com/pkgtest/pkg1"
b "github.com/pkgtest/pkg2"
)
func main() {
a.Foo()
b.Foo()
}
編譯執行上面程式將得到下面結果,而不是Error:
Foo of foo package in pkg1
Foo in foo package in pkg2
© 2016, bigwhite. 版權所有.
Related posts: