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 代码,打印输出:
笔者另外一篇文章,曾经详细介绍过 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
:
这段代码是一个简单的Go语言程序,它演示了如何使用sync.Map进行并发安全的键值对操作。下面是逐行解释这段代码的含义:
-
package main
:声明这个文件属于main包,表明这是一个可执行程序的入口文件。 -
import (
:引入需要使用的外部包,这里使用了"fmt"和"sync"包。 -
"fmt"
:引入了用于格式化输出的标准库包。 -
"sync"
:引入了用于同步操作的标准库包。 -
func main() {
:定义了程序的主函数。 -
var m sync.Map
:声明了一个变量m,类型为sync.Map。sync.Map是Go语言提供的一种并发安全的map类型,可以在多个goroutine中安全地读写数据,而无需额外的锁。 -
m.Store("key", "value")
:向map m中存储键值对,键为"key",值为"value"。 -
value, ok := m.Load("key")
:从map m中读取键为"key"的值,如果键存在,则将值赋给变量value,并将ok设置为true;如果键不存在,则value为nil,ok为false。这里使用了多重赋值的方式。 -
if ok {
:判断变量ok是否为true,如果为true,说明键存在。 -
fmt.Println(value)
:打印键"key"对应的值value。 -
}
: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)
}
执行之后的输出:
以下是对代码逐行解释的含义:
import (
"errors"
"fmt"
)
这里使用了import
关键字引入了两个标准库包。errors
包用于创建和处理错误信息,fmt
包用于格式化输出。
func divide(x, y int) (int, error) {
这行代码定义了一个名为divide
的函数。这个函数接受两个int
类型的参数x
和y
,并返回两个值,第一个值是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")
}
执行之后,打印的输出结果:
这段代码演示了在Go语言中如何使用defer
、panic
和recover
来处理运行时错误。具体来说,它包含了以下几个关键点:
-
recoverName()
函数定义了一个延迟函数,它通过调用recover()
函数来恢复从panic
状态中返回的错误信息。如果recover()
函数返回的值不为nil
,则说明有panic
发生,并且在该函数中进行了错误恢复处理。 -
fullName()
函数展示了如何在运行时可能出现错误的情况下使用panic
来中断程序执行,并在发生错误时抛出错误信息。在这个函数中,首先使用defer
关键字来延迟执行recoverName()
函数,以确保无论是否发生panic
,都能在函数退出时执行恢复处理。然后,通过检查传入的指针是否为nil
来判断是否存在运行时错误,如果存在错误,就会触发panic
,并且程序会跳转到最近的defer
语句执行。 -
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)
}
执行之后的代码输出:
这段代码演示了如何在Go语言中使用接口和结构体来实现多态性。具体来说,它包含了以下几个关键点:
-
Shape
接口:定义了一个Shape
接口,该接口包含一个Area()
方法,用于计算形状的面积。任何实现了Area()
方法的类型都可以被视为Shape
接口的实现者。 -
Rectangle
结构体:定义了一个Rectangle
结构体,包含了Width
和Height
两个字段,分别表示矩形的宽度和高度。此外,还实现了Area()
方法,用于计算矩形的面积。 -
Circle
结构体:定义了一个Circle
结构体,包含了Radius
字段,表示圆的半径。同样,它也实现了Area()
方法,用于计算圆的面积。 -
printArea()
函数:定义了一个printArea()
函数,接受一个Shape
类型的参数,并调用其Area()
方法来打印形状的面积。 -
main()
函数:程序的入口点。在这个函数中,创建了一个Rectangle
类型的变量rect
和一个Circle
类型的变量circle
,并分别传递给printArea()
函数进行面积打印。由于Rectangle
和Circle
类型都实现了Shape
接口的Area()
方法,因此它们都可以作为printArea()
函数的参数,展示了多态性的特性。
以上只是五个笔者工作中最经常使用到的 Go 特性分享,祝各位 Java 程序员的 Go 转型之路一切顺利。
- 点赞
- 收藏
- 关注作者
评论(0)