这篇文章帮助Java程序员快速入门Go语言。 本文将以一个有代表性的例子为开始,以此让Java程序员对Go语言有个初步认识,随后将详细的描述Go语言的各个构成模块,最后以一个例子来讲解Go语言与Java语言的不同点。 |
先来认识堆栈(例子)为了满足大家的好奇心,我们将开始于一个麻雀虽小但五脏内全的例子,这个例子将是Stack.java的Go语言版本。 //实现堆栈的包集合
package collection
//生成空堆栈
type Stack struct {
data []interface{}
}
//压栈顶
func (s *Stack) Push(x interface{}) {
s.data = append(s.data, x)
}
//弹栈顶,并删除栈顶
//如果是空栈将会引发运行时错误
func (s *Stack) Pop() interface{} {
i := len(s.data) - 1
res := s.data[i]
s.data[i] = nil // to avoid memory leak
s.data = s.data[:i]
return res
}
//返回栈大小
func (s *Stack) Size() int {
return len(s.data)
} - 用纯文本在声明语句上方添加注释。
- 申明的名字写在类型的后面。
- 结构体和Java中类相对应,但结构体的成员只能是变量,不能是方法。
- interface{}类型对应Java中的Object。它不仅能代表引用类型,同时也能实现Go语言的所有类型。
- (s *Stack)表示申明一个方法,s对应于Java中方法隐式传的this参数。
- 操作符:=表示申明和初始化一个变量,其类型由初始化语句决定。
|
下面是是使用collection.Stackabstract数据类型的简单例子。 package collection_test
import (
collection "."
"fmt"
)
func Example() {
var s collection.Stack
s.Push("world")
s.Push("hello, ")
for s.Size() > 0 {
fmt.Print(s.Pop())
}
fmt.Println()
// Output: hello, world
} 将测试包package colloection_test和collectionpackage放在同一目录。第一个import声明“.”表示将要用当前路径中的包并同时命名为collection,“fmt”是标准库的包名,没有重新命名,所以就直接用包名fmt即可。 |
概念上的不同点- Go没有类的构造器。Go提供结构体和接口,类的继承以及动态方法查询来进行实例化,而非通过实例方法。接口同样用于Java中的泛型。
- Go中不仅对象和数组可以有指向数据的指针,其它所有类型都有。对于任何类型T,都有一个指针类型*T与之相对应,其表明指向类型为T的值。
- Go允许在任何类型上创建方法;方法的接收者,就是Java中this,可以是个值或是指针。
- 数组在Go中是一些值。当数组作为函数参数传递时,函数将收到数组值的拷贝,不是指针。然而实际中,经常用slices作为参数;因为切片中有数组的引用。
- Go中提供字串类型;字串就像是由比特序列构成的slices,但字串是不可变的。
- 在Go中哈希表叫maps。
- Go提过channels类型来在多线程,goroutines之间通信来实现并发。
- 一般类型(maps,slices,channels)传参是引用,不是值。如果传参map,参数不是对map的值拷贝;如果函数里改变了map的值,函数调用者的map也将改变。这个和Java中map类似。
- Go中访问权限有两种,和Java中public 和包private类似。如果声明的名称首字母大写就是public,否则就是包private。
- Go用error类型取代Java中的异常,诸如访问文件到结尾、运行时刻panic,数组越界等等。
- Go不支持隐式类型转换,必须是显式的。
- Go不支持覆盖。函数名和方法名在同一作用域必须不同。
- Go用nil表示空指针,而Java用null。
|
语法 
声明声明的语法同Java比较是逆向的。你写上名字然后再跟着写类型。从左至右的类型声明也许读起来容易些。 var v1 int | int v1 = 0; | var v2 *int | Integer v2 = null; | var v3 string | String v3 = ""; | var v4 [10]int | int[] v4 = new int[10]; // v4 is a value in Go. | var v5 []int | int[] v5 = null; | var v6 *struct { a int } | C v6 = null; // Given: class C { int a; } | var v7 map[string]int | HashMap<String,Integer> v7 = null; | var v8 func(a int) int | F v8 = null; // interface F { int f(int a); } |
声明一般是采用一个关键字后面跟着被定义对象的名称,这种形式。关键字是const、type、var或者func其中之一。你也可以使用一个关键字后面跟着放在括号之中的一系列声明,这种形式。 |
当声明函数时,你必须为每个参数提供一个名称,或者不为任何参数提供名称;你不能省略一些参数的名称并提供其它参数的名称。你可以用相同的类型来组织几个名字,例如: func f(i, j, k int, s, t string) 一个变量可以在声明它时初始化值。进行此操作时,可以指定变量的类型,但这并非必需的。当不指定类型时,则默认为初始化表达式的类型。 如果一个变量没有明确地初始化,则必须指定其类型。在这种情况下,它将隐式地初始化为其类型的零值(0,nil 等)。在Go语言中没有其它某些语言中的未初始化的变量。 |
短声明在函数内,可以用 := 进行短声明,比如: 它等效于: 函数类型在Go语言中,函数是一等公民。Go的函数类型表示有相同的参数和结果类型的所有函数的集合。 type binOp func(int, int) int
var op binOp
add := func(i, j int) int { return i + j }
op = add
n = op(100, 200) // n = 100 + 200 |
多重赋值Go 允许多重赋值。右边的表达式先被求值然后赋给左边的操作数。 i, j = j, i // 交换 i 和 j 的值(不用象传统语言中需要第三个临时变量). 函数可以有多个返回值,用参数括号后面的一个括号中的列表表示。返回值通过赋予一个变量列表来存储,如: func f() (i int, pj *int) { ... }
v1, v2 = f() 空标识符空标识符用下划线字符表示,它提供了一种方法来忽略由多值表达式返回的某个值,如: v1, _ = f() // 忽略f()返回的第二个值. |
分号和格式不需要担心分号和格式,你可以用 gofmt程序创建一个标准的Go样式。虽然这个样式最初看起来或许有点古怪,但它同任何其它样式一样的好,而且熟悉以后会感觉越来越舒服。 在实践中Go代码使用分号不多。严格说,所有的Go语句都是由分号终止。不过,Go在非空白行的末尾隐式插入一个分号,除非该行明显未结束。这带来的影响是,在某些情况下Go不允许换行符。例如,你不能这么写 func g()
{ // INVALID; "{" should be on previous line.
} g()后面会插入一个分号,使它成为一个函数声明,而不是函数定义。类似的,你也不能写 if n == 0 {
}
else { // INVALID; "else {" should be on previous line.
} }后面else前面会插入一个分号,导致语法错误。 |
条件语句Go 不使用括号来包裹 if 语句中的条件,和 for 语句中的表达式, 以及 switch 语句中的值。但另一方面,它必须用花括号来包裹 if 或 for 的执行语句体。 if a < b { f() }
if (a < b) { f() } // 不需要括号.
if (a < b) f() // 非法(执行体未用花括号括起来)
for i = 0; i < 10; i++ {}
for (i = 0; i < 10; i++) {} // 非法(表达式不需要括起来) 此外,if 和 switch 接受一个可选的初始化语句,通常用它建立一个局部变量,如: if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
} |
For 语句 Go 不具有 while 语句,也没有 do-while 语句。可以赋以for语句一个单一条件,使其与while语句等效。完全省略条件会制造一个无限循环。 for语句可能包含一个范围条件,对strings, arrays, slices, maps, 或 channels进行迭代。除了这么写 for i := 0; i < len(a); i++ { ... } 要循环遍历a的元素,我们也可以这么写 for i, v := range a { ... } 这会将i作为索引,给v赋以一个array, slice, 或 string中的连续元素。对于字符串,i是对单一字节的索引,v是一个符文类型的Unicode代码点(符文是int32的一个别名)。对maps迭代会产生键值对,而对channels则只会产生一个迭代值。 |
Break和Continue 像Java一样,Go允许使用break和continue来指定标签(label),但是这个标签所引用的必须是一个for、switch或者select语句。 Switch语句在一个switch语句中,case标签默认不会往下传递(fall through,也就是在没有break的情况下也不会执行后续case的程序),但是你能够通过使用一个向下传递(fallthrough)语句来使得它们可以向下传递。 switch n {
case 0: // empty case body
case 1:
f() // f is not called when n == 0.
} 但是一个case能够有多个值: switch n {
case 0, 1:
f() // f is called if n == 0 || n == 1.
} case后面的值可以是支持相等比较(equality comparison)操作符的任何类型,比如string或者pointer。缺省的switch表达式等效于true表达式: switch {
case n < 0:
f1()
case n == 0:
f2()
default:
f3()
} |
++ 和 -- 语句++ 和 --只能被用作语句中的后缀操作符,而不能用在表达式中。例如,你不能再这样写了: n = i++。 defer 语句defer语句用来调用一个函数,但将其执行延迟到上一个附近的函数返回之后的时刻。被延迟的函数的执行与附近函数返回采取的路径无关。然而,当defer语句执行时,被延迟函数的参数已经被计算并保存以供之后使用。 f, err := os.Open("filename")
defer f.Close() // 当上个函数返回后,f 会被关闭. |
常量在 Go 中常量可以是无类型的。这适用于数值常量,只使用无类型常量的表达式,以及没有给出类型和初始化表达式是无类型的常量声明。当一个无类型的常量被用于一个需要类型化的值的环境中,它的值会转换成类型化的。所以即使 Go 没有隐式的类型转换,这也能允许常量被相对自由地使用。 var a uint
f(a + 1) // 无类型的数字常量 1 成为 uint 类型.
f(a + 1e3) // 1e3 也作为 uint 类型. 语言在无类型的数值常数的大小上不强加任何限制。 只有当一个常数被用在需要一个有类型的值时才会根据该类型对大小有所限制,如: const huge = 1 << 100
var n int = huge >> 98 |
如果在一个类型声明中类型关键字缺失,而相关的表达式计算出来是一个非类型(untyped)的数字常量,这个常量就会被分别转换成rune、int、float64、或者complex128类型,取决于这个值是否是一个字符(character)、整形(integer)、浮点型(float-point)还是复杂型(complex)的常量。 c := 'å' // rune (alias for int32)
n := 1 + 2 // int
x := 2.7 // float64
z := 1 + 2i // complex128 Go没有枚举(enumerate)类型。取而代之,你可以在一个单独的常量(const)声明中使用特殊的名字iota来获得一系列增长的值。当一个初始化表达简化成一个常量时,它就会重用前面的表达式。 const (
red = iota // red == 0
blue // blue == 1
green // green == 2
) |
结构结构对应于 Java 中的类,但结构的成员不能是方法,只能是变量。结构指针类似 Java 中的引用变量。与 Java 的类不同,结构也可以被定义为直接值。对于结构和结构指针都可以使用“.”来访问结构中的成员,如: type MyStruct struct {
s string
n int64
}
var x MyStruct // x 被初始化为 MyStruct{"", 0}.
var px *MyStruct // 指针 px 初始化为 nil.
px = new(MyStruct) // px 指向新结构体 MyStruct{"", 0}.
x.s = "Foo"
px.s = "Bar" 在 Go 中,方法可以与任意命名类型有关,不只与结构; 参考方法和接口的讨论. |
指针如果你有一个整数或一个结构或一个数组,赋值会复制对象的内容。Go 使用指针来实现 Java 的引用变量的效果。对于任意类型T,有一个相应的指针类型*T,表示指向类型T的值。 要为一个指针变量分配存储空间,要使用内置函数 new,它接受一个类型并返回一个指向已分配存储空间的指针。分配的空间将根据类型进行零初始化。 例如,new(int)为一个新int分配存储空间,初始值为0,并返回它的地址,类型为*int。
|
在Java代码 T p = new T()中,T是一个带有两个int类型实体变量a和b的类,对应于: type T struct { a, b int }
var p *T = new(T) 或者更地道的: 声明语句 var v T 声明了一个装着类型T的值的变量,在Java中没有相匹配的语句。值也可以使用一种复合语法来创建或者初始化,例如: 等效于: 对于类型T的一个操作数x,寻址操作符 &x 提供x的地址,它是类型*T的一个值。例如: p := &T{1, 2} // p has type *T 对于指针类型的一个操作数x,指针指向(pointer indirection)通过x用*x表示所指向的值。指针指向是很少被用到的;而Go像Java一样,能够自动获取到一个变量的地址: p := new(T)
p.a = 1 // equivalent to (*p).a = 1 |
Slices 一个slice是一个含有3个域的结构体:一个指向数组的指针,一个长度,一个容量大小。Slices可以用[]来访问数组元素。内置的len函数返回slice的长度,cap函数返回容量大小。 创建一个新的slice,可以用给定数组或slice a,通过a[i:j]的方式创建。新创建的slice是对a的引用,并且内容用是从a内容的索引的i到索引j。它的长度是j-i。如果i缺省,其 slice开始于0,j缺省表示len(a)。新的slice是原来a的引用,如果改变了新slice里元素的值,a也会改变。新slice的容量是a的容量减去i。其数值的容量是原数值的长度。 var s[]int
var a[10]int
s=a[:]//s=a[0:len(a)]的简写 如果创建了一个数[100]byte(100bytes的数组,也许用作缓存区),并且想将它传递给一个函数,那么可以将函数的参数设置为[]byte类型,这样就会传递一个slice。slice也可以通过make函数来创建(如下有描述)。 和Java中的ArrayList用法一样,slice也内建append函数。 s0:=[]int{1,2}
s1:=append(s0,3)//添加一个单元素
s2:=append(s1,4,5)//添加多元素
s3:=append(s2,s0...)//添加slice slice也能用于string,它将返回子字符串。 |
初始化Map和channel的值必须用内建的函数make来申请值。例如,用 将得到一个类型为map[string]int类型的值。于用new不同,make将返回对象值而不是地址。这样就于map和channel为引用类型就保持一致了。 对于map,make函数提供一个隐含可选的第二个参数。对于channel,也有第二个可选参数,它是用来设置channel缓冲区的大小;默认是0(没有缓冲区)。 make函数也能为slice来申请值。那样将申请一个隐藏在slice里数组,而返回的是指向它的slice引用。此时用make需要一个slice元素个数的参数。第二个可选参数是slice的容量。 m:=make([]int,10,20)//于new([20]int)[:10]相同 |
|