Go包導入與Java的差別

alex7742 8年前發布 | 10K 次閱讀 Java Go語言 Google Go/Golang開發

閑暇時翻閱了近期下載到的電子書 《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的細節可以參考我之前寫過的一篇文章《理解Golang包導入》。

我們先來看兩個功能等價的代碼。

//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. 版權所有.

 

來自:http://tonybai.com/2016/09/13/package-import-in-golang-vs-in-java/

 

 本文由用戶 alex7742 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!