设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Java程序员快速入门 go 语言

2013-7-22 11:17| 发布者: 红黑魂| 查看: 1739| 评论: 0|来自: 开源中国

摘要: 这篇文章帮助Java程序员快速入门Go语言。本文将以一个有代表性的例子为开始,以此让Java程序员对Go语言有个初步认识,随后将详细的描述Go语言的各个构成模块,最后以一个例子来讲解Go语言与Java语言的不同点。先来认 ...

方法和接口

方法

方法像是一个普通的函数定义,除非它有一个接收器(receiver)。接收器类似于Java实体方法中的this引用。

type MyType struct { i int }

func (p *MyType) Get() int {
    return p.i
}

var pm = new(MyType)
var n = pm.Get()

这里定义了一个同MyType联系起来的Get方法。名为p的接收器在函数体之中。

方法被定义在有命名的类型中。如果转换成不同类型的值,新的值将会拥有新类型的方法,而不是原有类型的那些方法。

你也许会在一个内建的类型中定义方法,通过声明一个继承自它的新命名的类型。新的类型同内建的类型是不同的。

type MyInt int

func (p MyInt) Get() int {
    return int(p)  // The conversion is required.
}

func f(i int) {}
var v MyInt

v = v * v          // The operators of the underlying type still apply.
f(int(v))          // int(v) has no defined methods.
f(v)               // INVALID

 

接口

Go的接口同Java的接口类似,但任何提供了用一个Go接口命名的方法的类型,都可以被看做是对那个接口的实现。不需要额外的声明了。

给定下面这个接口:

type MyInterface interface {
    Get() int
    Set(i int)
}
由于MyType已经有了一个Get方法,我们能够让Mytype满足这个接口的要求,通过添加:
func (p *MyType) Set(i int) {
    p.i = i
}
现在任何使用MyInterface作为参数的函数,将可以接受 *MyType类型的变量:
func GetAndSet(x MyInterface) {}

func f1() {
    var p MyType
    GetAndSet(&p)
}
在Java中,为*MyType类型定义Set和Get,就会让*MyType自动实现了MyInterface。一个类型可以满足多个接口。这是一种鸭式类型(duck typing)的形式。
当我看到一只鸟儿走起路来像鸭子,游起来也像鸭子,呱呱的叫起来也像鸭子,我就会把这只鸟儿称作鸭子.
James Whitcomb Riley
匿名域 

匿名域和Java中的子类类似.

type MySubType struct {
    MyType
    j int
}

func (p *MySubType) Get() int {
    p.j++
    return p.MyType.Get()
}
MySubType是作为MyType的子类。
func f2() {
    var p MySubType
 
    GetAndSet(&p)
}
Set方法继承于MyType,在闭合类型中的匿名域的方法可以提升为闭合类型的方法。也就是说,MyType做为MySubType的匿名域,MyType中的方法MySubType都能用。Get方法是覆盖方法,Set方法是继承来的。 
匿名域和也不是和Java中的子类完全一样。当调用匿名域的方法,方法的接收者是匿名域而不是闭合类型。也就是说,匿名域的方法不会动态分发。如果想实现Java中的动态方法looup,就等用interface。 
func f3() {
    var v MyInterface

    v = new(MyType)
    v.Get()  // Call the Get method for *MyType.

    v = new(MySubType)
    v.Get()  // Call the Get method for *MySubType.
}

类型断言

用类型断言可以将变量从一个接口类型转变为不同的接口类型。这是在运行时动态实现的。与Java不同,不需要对两个接口之间的关系作任何声明,如:

type Printer interface {
    Print()
}

func f4(x MyInterface) {
    x.(Printer).Print()  // 类型断言为 Printer
}

转换到Printer完全是动态的。 只要x的动态类型(存储在x中的值的实际类型)定义了一个Print方法,它就会工作。

泛型

Go 没有泛型类型,但通过结合匿名字段和类型断言可以实现类似于Java的参数化的类型,如:

type StringStack struct {
    Stack
}

func (s *StringStack) Push(n string) { s.Stack.Push(n) }
func (s *StringStack) Pop() string   { return s.Stack.Pop().(string) }

StringStack限定Hello stack例子中的泛型Stack,所以它只操作字符串元素——就像Java中的Stack<String> 。注意,Sizemethod继承于Stack。

 

错误

 

Java经常使用异常,Go则有两种机制。大多数程序只有真正不能回收的情况下返回错误,比如超出范围的索引,产生一个运行时异常。

Go的多值返回使得返回一个详细的错误消息和正常的返回值十分容易。按照惯例,这些消息有类型错误,一个简单的内置接口。

type error interface {
    Error() string
}
举个例子,如果打开文件失败,os.Open函数返回一个非空错误值。
func Open(name string) (file *File, err error)
下面的代码使用os.Open来打开一个文件。如果遇到错误就会调用log.Fatal来打印错误消息并终止。
f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

错误接口只需要一个Error方法,但是特定的错误实现往往会有附加的方法,允许调用者检查详细的错误。

 

Panic和recover

一个panic是一个运行时刻错误,并释放Go程序的堆栈,同时运行defer程序,最终程序停止。Panic和Java中的异常相似,但它仅仅表明是运行时错误,比如空指针或是数组越界。Go用内建错误类型来描述如访问文件结尾等上述错误信息。 

内建函数recover可用在panic并内恢复Go程序运行,同时recover能停止循环返回参数传递给panic。因为在defer函数里只能运行循环代码,同时revcover只能运行defer函数中。如果Go程序没有panic,recover将返回nil。

Go协程和信道


寿司制作流程 
 

Go协程

Go中的线程,使用go声明,执行一个goroutine.并且在不同的,新创建的goroutine中运行该函数.在一个程序中所有的Go协程,共用相同的地址空间.

Go协程是轻量级的,消耗成本只比分配的栈空间多一点, 栈开始时较小并通过堆存储的分配和释放来实现其增长。内部的Go协程像协程一样并存在操作系统的多个线程中。你不必去拘泥于这些细节。

go list.Sort()  // Run list.Sort in parallel; don’t wait for it.
Go 拥有函数字面量,可以表现为闭包函数,与go 声明一起使用的话,功能将更为强大.
// 发布后打印文本到标准输出,直到给定的时间过期。
func Publish(text string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(text)
    }()  // 注意括号,我们必须调用该函数.
}

变量text和delay在外部函数和闭包(函数字面量)之间共享,只存在于在它们可访问期间。

 

Channels

channel 通过传递特定元素类型的值,提供了一套两个 goroutines 同步执行及交流的机制。 <- 操作符制定 channel 发送或接收的方向。如果没有明确方向,则通道为双向的。

chan Sushi      // can be used to send and receive values of type Sushi
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints

Channel 为引用类型,使用 make 分配。

ic := make(chan int)        // unbuffered channel of ints
wc := make(chan *Work, 10)  // buffered channel of pointers to Work

要向通道传递值,以二进制操作符的方式使用 <- 。要接收数据,则以一元运算符的方式使用它。

ic <- 3       // Send 3 on the channel.
work := <-wc  // Receive a pointer to Work from the channel.

如若 channel 无缓冲区,则发送者堵塞,直到接收者接受到传值。如果 channel 有缓冲区,发送者堵塞,直到接收者开始读取缓冲区;如果缓冲区满了,则需要等到某些接收者开始检索值。接收者堵塞,直到有数据可接收。

close 函数将记录通道不能再被用来发送数据。当调用 close 函数后,在所有先前发送的值都被接收以后,接收操作将不会堵塞,同时返回 0 值。一个多值的接收操作将能够获取到 channel 是否被关闭的指示。

ch := make(chan string)
go func() {
    ch <- "Hello!"
    close(ch)
}()
fmt.Println(<-ch)  // Print "Hello!".
fmt.Println(<-ch)  // Print the zero value "" without blocking.
fmt.Println(<-ch)  // Once again print "".
v, ok := <-ch      // v is "", ok is false.

在下面的例子,我们将使 Publish 函数返回一个通道,它将被用来在文本发表完成后广播消息

// Publish prints text to stdout after the given time has expired.
// It closes the wait channel when the text has been published.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
    ch := make(chan struct{})
    go func() {
        time.Sleep(delay)
        fmt.Println(text)
        close(ch)
    }()
    return ch
}

下面就是 Publish 函数的大概用法

wait := Publish("important news", 2 * time.Minute)
// Do some more work.
<-wait // blocks until the text has been published

 

Select语句

select语句是Go统一工具箱中的最终工具。它选择哪些通信将被处理。如果任何的通信都能处理,那么就会随机选其一,与之对应的语句就会执行。另外,如果没有默认的case,语句就会阻塞直到其中一个通信完成为止。

这里有一个toy的例子,展示了select语句如何用来实现一个随机数发生器。

rand := make(chan int)
for { // Send random sequence of bits to rand.
    select {
    case rand <- 0: // note: no statement
    case rand <- 1:
    }
}
更加现实的是,这里有一个select语句可以用来设置时间限制一个接受操作。
select {
case news := <-AFP:
    fmt.Println(news)
case <-time.After(time.Minute):
    fmt.Println("Time out: no news in one minute.")
}

time.After函数式标准库的一部分;它等待一段特定时间后发送当前时间到返回的频道。

并发(示例)

最后我们通过一个小而全的例子展示如何将若干块拼凑在一起。它是一个服务器通过一个频道(channel)接受Work请求(Work request)的草案代码。每一个请求都使用一个单独的渠道进行处理。Work将自身构造包含进一个用来返回结果的频道中。

package server

import "log"

// New creates a new server that accepts Work requests
// through the req channel.
func New() (req chan<- *Work) {
    wc := make(chan *Work)
    go serve(wc)
    return wc
}

type Work struct {
    Op    func(int, int) int
    A, B  int
    Reply chan int  // Server sends result on this channel.
}

func serve(wc <-chan *Work) {
    for w := range wc {
        go safelyDo(w)
    }
}

func safelyDo(w *Work) {
    // Regain control of panicking goroutine to avoid
    // killing the other executing goroutines.
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(w)
}

func do(w *Work) {
    w.Reply <- w.Op(w.A, w.B)
}
而下面这是你如何使用它:
package server_test

import (
    server "."
    "fmt"
    "time"
)

func main() {
    s := server.New()

    divideByZero := &server.Work{
        Op:    func(a, b int) int { return a / b },
        A:     100,
        B:     0,
        Reply: make(chan int),
    }
    s <- divideByZero

    select {
    case res := <-divideByZero.Reply:
        fmt.Println(res)
    case <-time.After(time.Second):
        fmt.Println("No result in one second.")
    }
    // Output: No result in one second.
}
并发是一个庞大的话题,而Go的方法和Java的方法时相当不同的。有两篇文章涉及到了这些基础:

并发编程基础(Fundamentals of concurrent programming )使用Go编写的小例子来介绍并发。

通过交流共享内存(Share Memory by Communicating )使用更大幅度的例子进行代码走读(codewalk)。

Stefan Nilsson

英文原文:Go for Java programmers

参与翻译(8人)赵亮-本人yale8848LeoXusuper0555徐继开MtrSKhiyuan息心止念

Java程序员快速入门 go 语言

http://www.oschina.net/translate/go-for-java-programmers


酷毙
1

雷人

鲜花

鸡蛋
1

漂亮

刚表态过的朋友 (2 人)

  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部