Java 程序员极速上手 go

随着 Go 语言的流行,很多公司的技术栈在往 Go 上转,但很多招进来的后端开发工程师都是 Java 技术栈,然后在工作中边学边上手。

那么 Java 程序员要想极速上手 Go,应该从哪些方面入手呢?

对于已经有一定基础的 Java 工程师,可以思考自己以前用 Java 编程时,最常使用的语言特性,列一个清单出来。然后按照这个清单,去学习 Go 语言的对应实现方式,这样能够有针对性的的学习,有的放矢。

下面是我使用 Java 进行日常工作中经常使用的5个编程要点,我会介绍这些要点对应的 Go 实现,仅供大家参考。

1. 并发编程(Concurrency)

Go语言通过goroutine和channel提供了轻量级的并发编程模型,与Java的线程模型相比更加简洁和高效。下面是一个简单的并发示例:

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建一个 go routine
    go sayHello("World")
    
    // 主 goroutine 打印 Hello
    sayHello("Gopher")
}

func sayHello(name string) {
    for i := 0; i < 5; i++ {
        fmt.Println("Hello", name)
        time.Sleep(time.Second)
    }
}

执行上述 go 代码,打印输出:

Java 程序员极速上手 go_第1张图片

笔者另外一篇文章,曾经详细介绍过 Go 的并发编程,大家可以移步我这篇腾讯云社区文章:

通过三个例子,学习 Go 语言并发编程的利器 - goroutine

2. 内置并发安全的Map(Built-in concurrency-safe Map)

在Go语言中,内置了并发安全的map类型(sync.Map),可以在多个goroutine中安全地读写数据,而无需额外的锁。以下是一个使用sync.Map的示例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    
    // 写入键值对
    m.Store("key", "value")
    
    // 读取键对应的值
    value, ok := m.Load("key")
    if ok {
        fmt.Println(value)
    }
}

执行上述代码,最后打印 value

Java 程序员极速上手 go_第2张图片

这段代码是一个简单的Go语言程序,它演示了如何使用sync.Map进行并发安全的键值对操作。下面是逐行解释这段代码的含义:

  1. package main:声明这个文件属于main包,表明这是一个可执行程序的入口文件。
  2. import (:引入需要使用的外部包,这里使用了"fmt"和"sync"包。
  3. "fmt":引入了用于格式化输出的标准库包。
  4. "sync":引入了用于同步操作的标准库包。
  5. func main() {:定义了程序的主函数。
  6. var m sync.Map:声明了一个变量m,类型为sync.Map。sync.Map是Go语言提供的一种并发安全的map类型,可以在多个goroutine中安全地读写数据,而无需额外的锁。
  7. m.Store("key", "value"):向map m中存储键值对,键为"key",值为"value"。
  8. value, ok := m.Load("key"):从map m中读取键为"key"的值,如果键存在,则将值赋给变量value,并将ok设置为true;如果键不存在,则value为nil,ok为false。这里使用了多重赋值的方式。
  9. if ok {:判断变量ok是否为true,如果为true,说明键存在。
  10. fmt.Println(value):打印键"key"对应的值value。
  11. }:if语句的结束。

整个程序的逻辑很简单,就是先向sync.Map中存储了一个键值对,然后再从中读取出来并打印出对应的值。由于sync.Map是并发安全的,所以可以在多个goroutine中安全地进行这些操作。

3. 错误处理(Error Handling)

Go语言采用显式的错误处理机制,通过返回error类型来传递错误信息,而不是Java中的异常。下面是一个简单的错误处理示例:

package main

import (
    "errors"
    "fmt"
)

func divide(x, y int) (int, error) {
    if y == 0 {
        return 0, errors.New("division by zero")
    }
    return x / y, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

执行之后的输出:

Java 程序员极速上手 go_第3张图片

以下是对代码逐行解释的含义:

import (
    "errors"
    "fmt"
)

这里使用了import关键字引入了两个标准库包。errors包用于创建和处理错误信息,fmt包用于格式化输出。

func divide(x, y int) (int, error) {

这行代码定义了一个名为divide的函数。这个函数接受两个int类型的参数xy,并返回两个值,第一个值是int类型的商,第二个值是error类型的错误信息(如果有错误的话)。

if y == 0 {
    return 0, errors.New("division by zero")
}

在函数内部,首先检查y是否为零。如果y为零,就会调用errors.New函数创建一个新的错误,其中包含字符串"division by zero",然后返回0和该错误。

return x / y, nil

如果y不为零,则返回x / y作为商,并且返回nil表示没有错误发生。

func main() {

这行代码定义了程序的主函数。

result, err := divide(10, 0)

在主函数中,调用了divide函数,传入了参数10和0。result接收到divide函数返回的商,err接收到可能返回的错误。

if err != nil {

检查err是否不为nil,如果不为nil,说明函数调用过程中发生了错误。

fmt.Println("Error:", err)

如果有错误发生,使用fmt.Println打印错误信息。

return

然后通过return语句结束函数执行。

fmt.Println("Result:", result)

如果没有发生错误,则打印商的结果。

整体而言,这段代码展示了一个简单的除法函数,并演示了如何处理可能的错误情况。

4. defer和panic/recover(defer and panic/recover)

Go语言提供了defer关键字用于延迟执行函数调用,以及panic和recover机制用于处理异常。以下是一个使用defer和panic/recover的示例:

package main

import "fmt"

func recoverName() {
    if r := recover(); r != nil {
        fmt.Println("recovered from", r)
    }
}

func fullName(firstName *string, lastName *string) {
    defer recoverName()
    if firstName == nil {
        panic("runtime error: first name cannot be nil")
    }
    if lastName == nil {
        panic("runtime error: last name cannot be nil")
    }
    fmt.Printf("%s %s\n", *firstName, *lastName)
    fmt.Println("returned normally from fullName")
}

func main() {
    defer fmt.Println("deferred call in main")
    firstName := "John"
    fullName(&firstName, nil)
    fmt.Println("returned normally from main")
}

执行之后,打印的输出结果:

Java 程序员极速上手 go_第4张图片

这段代码演示了在Go语言中如何使用deferpanicrecover来处理运行时错误。具体来说,它包含了以下几个关键点:

  1. recoverName()函数定义了一个延迟函数,它通过调用recover()函数来恢复从panic状态中返回的错误信息。如果recover()函数返回的值不为nil,则说明有panic发生,并且在该函数中进行了错误恢复处理。
  2. fullName()函数展示了如何在运行时可能出现错误的情况下使用panic来中断程序执行,并在发生错误时抛出错误信息。在这个函数中,首先使用defer关键字来延迟执行recoverName()函数,以确保无论是否发生panic,都能在函数退出时执行恢复处理。然后,通过检查传入的指针是否为nil来判断是否存在运行时错误,如果存在错误,就会触发panic,并且程序会跳转到最近的defer语句执行。
  3. main()函数是程序的入口点。在这个函数中,通过使用defer语句来延迟执行fmt.Println("deferred call in main"),确保该语句会在函数退出时被执行。然后定义了一个firstName变量,并调用了fullName()函数,将firstName的地址和一个nil指针作为参数传递给该函数。由于lastName参数为nil,因此会触发panic,并且程序会跳转到fullName()函数中执行错误处理。最后,fmt.Println("returned normally from main")语句会在main()函数正常返回时执行,但由于在调用fullName()函数时已经发生了panic,因此该语句不会被打印出来。

5. 结构体(Structs)和接口(Interfaces)

Go语言中的结构体和接口是非常灵活和强大的,可以有效地组织代码和实现多态性。下面是一个使用结构体和接口的示例:

package main

import (
    "fmt"
)

type Shape interface {
    Area() float64
}

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

func printArea(s Shape) {
    fmt.Println("Area:", s.Area())
}

func main() {
    rect := Rectangle{Width: 5, Height: 3}
    circle := Circle{Radius: 4}
    
    printArea(rect)
    printArea(circle)
}

执行之后的代码输出:

Java 程序员极速上手 go_第5张图片

这段代码演示了如何在Go语言中使用接口和结构体来实现多态性。具体来说,它包含了以下几个关键点:

  1. Shape接口:定义了一个Shape接口,该接口包含一个Area()方法,用于计算形状的面积。任何实现了Area()方法的类型都可以被视为Shape接口的实现者。
  2. Rectangle结构体:定义了一个Rectangle结构体,包含了WidthHeight两个字段,分别表示矩形的宽度和高度。此外,还实现了Area()方法,用于计算矩形的面积。
  3. Circle结构体:定义了一个Circle结构体,包含了Radius字段,表示圆的半径。同样,它也实现了Area()方法,用于计算圆的面积。
  4. printArea()函数:定义了一个printArea()函数,接受一个Shape类型的参数,并调用其Area()方法来打印形状的面积。
  5. main()函数:程序的入口点。在这个函数中,创建了一个Rectangle类型的变量rect和一个Circle类型的变量circle,并分别传递给printArea()函数进行面积打印。由于RectangleCircle类型都实现了Shape接口的Area()方法,因此它们都可以作为printArea()函数的参数,展示了多态性的特性。

以上只是五个笔者工作中最经常使用到的 Go 特性分享,祝各位 Java 程序员的 Go 转型之路一切顺利。

你可能感兴趣的:(Java 程序员极速上手 go)