分类 技术志 下的文章

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的细节可以参考我之前写过的一篇文章《理解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

vim-go更新小记

自从上一次配置好Mac上的Golang Vim开发环境,基本上就没怎么动过。近两年过去了,Go已经升级到了1.7版本Vim-go截至目前也已经演化到了1.8版本了。社区的积极关注和使用,让Vim-go的作者Fatih Arslan备受鼓舞,于是近一年来,积极为vim-go添加新功能,发布新版本,并编写了vim-go的详细tutorial。这让我动了更新Vim-go版本的念头,于是就有了本篇内容。

已经记不得当初第一次配置vim-go时,vim-go的版本号是多少了。经过近两年的发展,vim-go已然正式成为Vim下Go开发环境的标准Plugin了。Go从当年的1.4升级到1.7,相关工具也跟着一起升级,比如oracle变成了guru,名字都换了。支持go的编辑器也逐渐增多并日益成熟,从最初vimliteIDE,到后来的eclipseIntelliJ Ideaatomsublime text以及vscode对golang都提供了支持。这样一来,无论你之前是哪种IDE的拥趸,你都能找到得心应手的环境走入Golang世界。

我个人一直用vim,sublime text3曾经玩过,没玩熟,卸了。目前机器上还装了一份vscode,感觉在IDE领域中,微软的影响力和成熟度不容小觑,vscode + golang extension从入门门槛来看,还是非常低的。即便vim-go进化到1.8版本,仍然不如vscode安装体验来得方便。当然这不全是vim-go的问题,而是vim的设计哲学所致。

无论是vim-go还是vscode golang plugin,都要依赖golang的周边工具,主要包括gocodegoimportsgurugodefgolintgometalinter等。在这方面,vim-go提供了安装依赖工具的方法“:GoInstallBinaries”,或在外部通过:vim -c “GoInstallBinaries” -c “qa”安装(在安装vim-go之后);而vscode则会自动探测其所依赖的工具是否安装,如果没有安装,会在vscode的下方给出提示,点击提示,会安装相应的工具。

BTW,自从近期golang官网:golang.org不用再翻墙后,go get下载golang.org域名下面的各种工具也简单了许多,大陆的Gopher们再也无需担心go package下载的问题了。

升级vim-go之前,建议先备份好.vimrc文件:

cp .vimrc .vimrc.bak.20160908

vim-go插件安装由很多方法,在vim-go tutorial中,vim-go作者选择了vim-plug,而没有用之前的vim插件管理工具vundle.vim,方法都是大同小异:

下载vim-plug:

$curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 67682  100 67682    0     0   7020      0  0:00:09  0:00:09 --:--:-- 12576

安装vim-go:

在.vimrc中填写如下内容:

call plug#begin()
Plug 'fatih/vim-go'

然后执行”:PlugInstall”即可。

在安装依赖工具期间,发现mac原生自带的vim(macvim,又叫mvim,安装在/usr/local/bin/mvim)版本还是7.3.xx版本,无法满足一些工具的要求,于是通过brew安装vim(安装在/usr/local/Cellar/vim/7.4.2334/bin/vim),然后通过/usr/bin/vim的一个符号链接连过去即可。

$ll /usr/bin|grep vim
lrwxr-xr-x     1 root   wheel        38  9  8 16:21 vim@ -> /usr/local/Cellar/vim/7.4.2334/bin/vim
... ...

注意,考虑要安装neocomplete以支持实时completion(补齐),vim需要有lua支持,因此执行brew install时要带上–with-lua参数:

brew install vim --with-lua

vim-go升级版安装后,可按照vim-go-tutorial中的步骤,体验一下vim-go的强大,同时对.vimrc进行相关配置,并安装缺失的vim插件,比如neocomplete、UltiSnips等。我针对vim-go 1.8配置好的.vimrc在这里可以下载到。

具体细节这里就不提了,如果还有哪些细节不清楚或实验没成功,可以回过头参考我那篇《Golang开发环境搭建-Vim篇》。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats