跳到主要内容
Go 语言极简教程:Java 开发者视角入门实战 | 极客日志
Go / Golang java
Go 语言极简教程:Java 开发者视角入门实战 Go 语言与 Java 对比教程,涵盖环境搭建、基础语法、函数方法、复合数据类型、接口组合、错误处理、并发编程及包管理。通过 RESTful API 实战示例展示 Go Gin 框架与 Spring Boot 实现差异,重点解析 Goroutine 轻量级线程、多返回值错误处理及隐式接口等核心特性,帮助 Java 开发者快速迁移至云原生开发场景。
月光旅人 发布于 2026/2/7 更新于 2026/6/2 22 浏览为什么 Java 开发者要学 Go?
在云原生时代,Go 语言已成为容器编排、微服务架构和高性能后端的首选语言之一。作为 Java 开发者,你可能已经习惯了 JVM 的强大生态,但在面对高并发、低延迟的场景时,Go 的轻量级线程(goroutine)和原生并发模型展现出独特优势。
本文专为 Java 开发者设计,通过对比你熟悉的 Java 语法和编程模式,快速掌握 Go 语言核心特性。我们将跳过 Hello World 式的基础,直接深入实战开发中最常用的 Go 语言特性,让你在最短时间内具备 Go 项目开发能力。
环境搭建与开发工具
安装 Go
Go 语言官网提供了各平台的安装包,最新稳定版本为 1.22.5(截至 2025 年 9 月):
wget https://dl.google.com/go/go1.22.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
go version
推荐开发工具
GoLand :JetBrains 专为 Go 开发的 IDE,提供卓越的代码提示和调试功能
VS Code + Go 插件:轻量级选择,适合习惯 VS Code 的开发者
项目结构
Go 项目结构与 Maven 项目有显著差异,标准布局如下:
myproject/
├── go .mod
├── go .sum
├── main.go
├── internal/
│ └── utils/
├── pkg/
└── api/
基础语法对比
变量声明与初始化
Java 中我们这样声明变量:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VariableDemo {
public static void main (String[] args) {
String ;
;
;
price;
price = ;
log.info( , message);
}
}
message
=
"Hello Java"
int
count
=
10
boolean
flag
=
true
double
99.99
"Message: {}"
package main
import "fmt"
func main () {
message := "Hello Go"
count := 10
flag := true
var price float64
price = 99.99
fmt.Printf("Message: %s\n" , message)
}
Go 使用 := 进行短变量声明,自动推断类型
Go 变量声明后默认初始化(数值类型为 0,字符串为空,布尔为 false)
Go 没有 public/private 关键字,通过首字母大小写控制可见性(大写导出,小写私有)
基本数据类型 Go 的基本数据类型与 Java 有相似之处,但也有重要区别:
package main
import "fmt"
func main () {
var a int8 = 127
var b uint16 = 65535
var c int = 1000
var pi float64 = 3.1415926535
isActive := true
name := "Go 语言"
fmt.Printf("Type of name: %T, Value: %s\n" , name, name)
}
Go 的整数类型有明确长度(int8, int16, int32, int64),Java 的 int 固定 32 位
Go 的 rune 类型对应 Java 的 char,但用于表示 Unicode 码点
Go 字符串是不可变的字节序列,与 Java 类似,但原生支持 UTF-8
控制流语句
条件语句 import lombok.extern.slf4j.Slf4j;
@Slf4j
public class IfDemo {
public static void main (String[] args) {
int score = 85 ;
if (score >= 90 ) {
log.info("优秀" );
} else if (score >= 60 ) {
log.info("及格" );
} else {
log.info("不及格" );
}
}
}
package main
import "fmt"
func main () {
score := 85
if score >= 90 {
fmt.Println("优秀" )
} else if score >= 60 {
fmt.Println("及格" )
} else {
fmt.Println("不及格" )
}
if num := 10 ; num%2 == 0 {
fmt.Println("偶数" )
}
}
注意 :Go 的 if 语句条件不需要括号,但必须有大括号,即使只有一行代码。
循环语句 import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoopDemo {
public static void main (String[] args) {
for (int i = 0 ; i < 5 ; i++) {
log.info("i: {}" , i);
}
String[] fruits = {"苹果" , "香蕉" , "橙子" };
for (String fruit : fruits) {
log.info("水果:{}" , fruit);
}
}
}
package main
import "fmt"
func main () {
for i := 0 ; i < 5 ; i++ {
fmt.Printf("i: %d\n" , i)
}
j := 0
for j < 5 {
fmt.Printf("j: %d\n" , j)
j++
}
fruits := []string {"苹果" , "香蕉" , "橙子" }
for index, fruit := range fruits {
fmt.Printf("索引:%d, 水果:%s\n" , index, fruit)
}
for _, fruit := range fruits {
fmt.Printf("水果:%s\n" , fruit)
}
}
Go 没有 while 和 do-while,所有循环都用 for 实现
range 关键字提供了便捷的集合遍历方式,类似 Java 的增强 for 循环
可以使用 _ 忽略不需要的返回值(Go 不允许声明未使用的变量)
函数与方法
函数定义 import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
@Slf4j
public class FunctionDemo {
public static int add (int a, int b) {
return a + b;
}
public static String concat (String s1, String s2) {
if (!StringUtils.hasText(s1)) {
return s2;
}
if (!StringUtils.hasText(s2)) {
return s1;
}
return s1 + s2;
}
public static void main (String[] args) {
int sum = add(3 , 5 );
log.info("3 + 5 = {}" , sum);
String result = concat("Hello" , "World" );
log.info("拼接结果:{}" , result);
}
}
package main
import "fmt"
func add (a int , b int ) int {
return a + b
}
func concat (s1, s2 string ) string {
if s1 == "" {
return s2
}
if s2 == "" {
return s1
}
return s1 + s2
}
func main () {
sum := add(3 , 5 )
fmt.Printf("3 + 5 = %d\n" , sum)
result := concat("Hello" , "World" )
fmt.Printf("拼接结果:%s\n" , result)
}
Go 函数支持多返回值,这是与 Java 最显著的区别之一
Go 函数参数可以合并声明类型,更简洁
Go 没有 static 关键字,函数是否属于类型取决于是否定义为方法
多返回值 Go 的多返回值特性非常适合处理函数执行结果和错误信息:
package main
import "fmt"
func divide (a, b float64 ) (float64 , float64 , error ) {
if b == 0 {
return 0 , 0 , fmt.Errorf("除数不能为 0" )
}
quotient := a / b
remainder := a % b
return quotient, remainder, nil
}
func main () {
quotient, remainder, err := divide(10 , 3 )
if err != nil {
fmt.Printf("错误:%v\n" , err)
} else {
fmt.Printf("10 ÷ 3 = %v, 余数:%v\n" , quotient, remainder)
}
q, r, e := divide(5 , 0 )
if e != nil {
fmt.Printf("错误:%v\n" , e)
} else {
fmt.Printf("5 ÷ 0 = %v, 余数:%v\n" , q, r)
}
res, _, _ := divide(8 , 2 )
fmt.Printf("8 ÷ 2 = %v\n" , res)
}
在 Java 中,我们通常需要用自定义对象或异常来实现类似功能,相比之下 Go 的多返回值更加直观:
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DivisionDemo {
@Data
public static class DivisionResult {
private double quotient;
private double remainder;
}
public static DivisionResult divide (double a, double b) {
if (b == 0 ) {
throw new IllegalArgumentException ("除数不能为 0" );
}
DivisionResult result = new DivisionResult ();
result.setQuotient(a / b);
result.setRemainder(a % b);
return result;
}
public static void main (String[] args) {
try {
DivisionResult result = divide(10 , 3 );
log.info("10 ÷ 3 = {}, 余数:{}" , result.getQuotient(), result.getRemainder());
} catch (IllegalArgumentException e) {
log.error("错误:{}" , e.getMessage());
}
}
}
方法 Go 中的方法与函数类似,但方法是与特定类型关联的函数,类似于 Java 中的实例方法:
package main
import "fmt"
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c *Circle) Scale(factor float64 ) {
c.Radius *= factor
}
func main () {
circle := Circle{Radius: 5 }
fmt.Printf("圆的面积:%.2f\n" , circle.Area())
circle.Scale(2 )
fmt.Printf("缩放后的半径:%.2f\n" , circle.Radius)
fmt.Printf("缩放后的面积:%.2f\n" , circle.Area())
}
Go 的方法定义需要指定接收者(receiver),类似 Java 中的 this
值接收者与指针接收者的区别:值接收者操作副本,指针接收者操作原对象
Go 没有类继承,但可以通过结构体嵌套实现类似功能
复合数据类型
数组与切片 import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class ArrayDemo {
public static void main (String[] args) {
int [] numbers = new int [5 ];
numbers[0 ] = 1 ;
numbers[1 ] = 2 ;
String[] fruits = {"苹果" , "香蕉" , "橙子" };
log.info("水果数量:{}" , fruits.length);
List<String> fruitList = Arrays.asList(fruits);
if (!CollectionUtils.isEmpty(fruitList)) {
for (String fruit : fruitList) {
log.info("水果:{}" , fruit);
}
}
}
}
package main
import "fmt"
func main () {
var numbers [5 ]int
numbers[0 ] = 1
numbers[1 ] = 2
fmt.Printf("数组长度:%d\n" , len (numbers))
fruits := [3 ]string {"苹果" , "香蕉" , "橙子" }
var veggies []string
veggies = append (veggies, "胡萝卜" )
veggies = append (veggies, "黄瓜" , "西红柿" )
fmt.Printf("蔬菜数量:%d\n" , len (veggies))
fruitSlice := fruits[1 :3 ]
fmt.Println("水果切片:" )
for _, fruit := range fruitSlice {
fmt.Printf("- %s\n" , fruit)
}
nums := make ([]int , 3 , 5 )
nums[0 ] = 10
nums[1 ] = 20
nums[2 ] = 30
fmt.Printf("nums 长度:%d, 容量:%d\n" , len (nums), cap (nums))
nums = append (nums, 40 )
fmt.Printf("添加元素后 - 长度:%d, 容量:%d\n" , len (nums), cap (nums))
}
数组长度固定,声明后不可变
切片是动态的,可以通过 append 函数增长
切片有长度(当前元素数量)和容量(可容纳的元素数量)
当切片容量不足时,append 会自动扩容(通常翻倍)
映射(Map) import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.Map;
@Slf4j
public class MapDemo {
public static void main (String[] args) {
Map<String, Integer> scores = Maps.newHashMap();
scores.put("张三" , 90 );
scores.put("李四" , 85 );
scores.put("王五" , 95 );
if (scores.containsKey("李四" )) {
log.info("李四的分数:{}" , scores.get("李四" ));
}
if (!CollectionUtils.isEmpty(scores)) {
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
log.info("{}: {}" , entry.getKey(), entry.getValue());
}
}
scores.remove("王五" );
log.info("删除王五后,是否还存在:{}" , scores.containsKey("王五" ));
}
}
package main
import "fmt"
func main () {
scores := make (map [string ]int )
scores["张三" ] = 90
scores["李四" ] = 85
scores["王五" ] = 95
if score, exists := scores["李四" ]; exists {
fmt.Printf("李四的分数:%d\n" , score)
}
for name, score := range scores {
fmt.Printf("%s: %d\n" , name, score)
}
delete (scores, "王五" )
if _, exists := scores["王五" ]; !exists {
fmt.Println("王五已被删除" )
}
}
Go 的 map 没有内置的排序,遍历顺序不固定
Go 通过 value, exists := map[key] 模式检查键是否存在
Go 使用 delete 内置函数删除元素
两者都不允许重复键,新值会覆盖旧值
结构体(Struct) 结构体是 Go 中实现数据封装的主要方式,类似 Java 中的类:
package main
import "fmt"
type User struct {
ID int
Username string
Email string
Age int
}
func (u User) Greet() string {
return fmt.Sprintf("Hello, my name is %s" , u.Username)
}
func (u *User) UpdateEmail(newEmail string ) {
u.Email = newEmail
}
func main () {
user1 := User{
ID: 1 ,
Username: "张三" ,
Email: "[email protected] " ,
Age: 30 ,
}
fmt.Printf("用户名:%s, 年龄:%d\n" , user1.Username, user1.Age)
fmt.Println(user1.Greet())
user1.UpdateEmail("[email protected] " )
fmt.Printf("更新后的邮箱:%s\n" , user1.Email)
tempUser := struct {
Name string
Score int
}{
Name: "临时用户" ,
Score: 95 ,
}
fmt.Printf("临时用户:%s, 分数:%d\n" , tempUser.Name, tempUser.Score)
}
结构体没有构造函数,通常通过工厂函数创建实例
结构体字段默认是导出的(大写)或私有(小写)
结构体可以嵌套,实现类似继承的功能
没有 class 关键字,结构体更轻量
面向对象特性 Go 不是纯粹的面向对象语言,它没有类和继承的概念,但通过结构体、接口和组合实现了类似的功能。
接口(Interface) import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InterfaceDemo {
public interface Shape {
double calculateArea () ;
double calculatePerimeter () ;
}
public static class Circle implements Shape {
private double radius;
public Circle (double radius) {
this .radius = radius;
}
@Override
public double calculateArea () {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter () {
return 2 * Math.PI * radius;
}
}
public static class Rectangle implements Shape {
private double width;
private double height;
public Rectangle (double width, double height) {
this .width = width;
this .height = height;
}
@Override
public double calculateArea () {
return width * height;
}
@Override
public double calculatePerimeter () {
return 2 * (width + height);
}
}
public static void main (String[] args) {
Shape circle = new Circle (5 );
log.info("圆面积:{}" , circle.calculateArea());
log.info("圆周长:{}" , circle.calculatePerimeter());
Shape rectangle = new Rectangle (4 , 6 );
log.info("矩形面积:{}" , rectangle.calculateArea());
log.info("矩形周长:{}" , rectangle.calculatePerimeter());
}
}
package main
import "fmt"
type Shape interface {
CalculateArea() float64
CalculatePerimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) CalculateArea() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) CalculatePerimeter() float64 {
return 2 * 3.14159 * c.Radius
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) CalculateArea() float64 {
return r.Width * r.Height
}
func (r Rectangle) CalculatePerimeter() float64 {
return 2 * (r.Width + r.Height)
}
func PrintShapeInfo (s Shape) {
fmt.Printf("面积:%.2f, 周长:%.2f\n" , s.CalculateArea(), s.CalculatePerimeter())
}
func main () {
var shape Shape
shape = Circle{Radius: 5 }
fmt.Print("圆 - " )
PrintShapeInfo(shape)
shape = Rectangle{Width: 4 , Height: 6 }
fmt.Print("矩形 - " )
PrintShapeInfo(shape)
}
Go 的接口实现是隐式的,不需要 implements 关键字
Go 接口只定义方法签名,不包含字段
Go 支持空接口 interface{},可以表示任何类型,类似 Java 的 Object
Go 接口可以嵌套组合成新的接口
组合(Composition) Go 通过结构体嵌套实现代码复用,替代了传统的继承:
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) Greet() string {
return fmt.Sprintf("Hello, I'm %s" , p.Name)
}
type Student struct {
Person
School string
Grade int
}
type Teacher struct {
Person
Subject string
Students int
}
func (s Student) Study() string {
return fmt.Sprintf("%s is studying at %s" , s.Name, s.School)
}
func (t Teacher) Greet() string {
return fmt.Sprintf("Hello, I'm %s, teaching %s" , t.Name, t.Subject)
}
func main () {
student := Student{
Person: Person{
Name: "张三" ,
Age: 15 ,
},
School: "第一中学" ,
Grade: 9 ,
}
fmt.Println(student.Greet())
fmt.Println(student.Study())
fmt.Printf("年龄:%d\n" , student.Age)
teacher := Teacher{
Person: Person{
Name: "李四" ,
Age: 35 ,
},
Subject: "数学" ,
Students: 45 ,
}
fmt.Println(teacher.Greet())
fmt.Printf("教授科目:%s\n" , teacher.Subject)
}
Go 使用组合而非继承实现代码复用
嵌套结构体的字段和方法可以直接访问,类似继承
可以重写嵌套结构体的方法(同名方法会覆盖)
可以嵌套多个结构体,实现多 "继承" 效果
错误处理 Go 的错误处理机制与 Java 的异常处理有很大不同,Go 采用返回值方式处理错误,而非异常抛出。
基本错误处理 import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
@Slf4j
public class ErrorHandlingDemo {
public static double divide (double a, double b) {
if (b == 0 ) {
throw new ArithmeticException ("除数不能为 0" );
}
return a / b;
}
public static boolean login (String username, String password) {
if (!StringUtils.hasText(username)) {
throw new IllegalArgumentException ("用户名不能为空" );
}
if (!StringUtils.hasText(password)) {
throw new IllegalArgumentException ("密码不能为空" );
}
return "admin" .equals(username) && "password" .equals(password);
}
public static void main (String[] args) {
try {
double result = divide(10 , 0 );
log.info("结果:{}" , result);
} catch (ArithmeticException e) {
log.error("除法错误:{}" , e.getMessage());
}
try {
boolean success = login("" , "123456" );
log.info("登录成功:{}" , success);
} catch (IllegalArgumentException e) {
log.error("登录错误:{}" , e.getMessage());
}
}
}
package main
import (
"errors"
"fmt"
)
func divide (a, b float64 ) (float64 , error ) {
if b == 0 {
return 0 , errors.New("除数不能为 0" )
}
return a / b, nil
}
func login (username, password string ) (bool , error ) {
if username == "" {
return false , errors.New("用户名不能为空" )
}
if password == "" {
return false , errors.New("密码不能为空" )
}
return username == "admin" && password == "password" , nil
}
func main () {
result, err := divide(10 , 0 )
if err != nil {
fmt.Printf("除法错误:%v\n" , err)
} else {
fmt.Printf("结果:%v\n" , result)
}
success, err := login("" , "123456" )
if err != nil {
fmt.Printf("登录错误:%v\n" , err)
} else {
fmt.Printf("登录成功:%v\n" , success)
}
}
Go 使用返回值传递错误,Java 使用异常抛出
Go 错误处理更显式,强制开发者处理可能的错误
Go 1.13 + 支持错误包装(fmt.Errorf("...: %w", err))和错误判断(errors.Is、errors.As)
Java 的异常可以中断程序执行流程,Go 的错误需要显式检查
自定义错误类型 Go 允许定义自定义错误类型,以便更精确地处理不同错误情况:
package main
import (
"fmt"
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s 验证失败:%s" , e.Field, e.Message)
}
type DatabaseError struct {
Operation string
Err error
}
func (e *DatabaseError) Error() string {
return fmt.Sprintf("数据库操作 %s 失败:%v" , e.Operation, e.Err)
}
func validateUser (username, email string ) error {
if username == "" {
return &ValidationError{Field: "username" , Message: "不能为空" }
}
if len (username) < 3 {
return &ValidationError{Field: "username" , Message: "长度不能小于 3" }
}
if email == "" {
return &ValidationError{Field: "email" , Message: "不能为空" }
}
if len (email) < 5 || email[len (email)-4 :] != ".com" {
return &ValidationError{Field: "email" , Message: "格式不正确" }
}
return nil
}
func saveUser (username, email string ) error {
if username == "admin" {
return &DatabaseError{
Operation: "saveUser" ,
Err: fmt.Errorf("用户名 %s 已存在" , username),
}
}
return nil
}
func main () {
username := "ad"
email := "test"
if err := validateUser(username, email); err != nil {
if valErr, ok := err.(*ValidationError); ok {
fmt.Printf("验证错误:%s\n" , valErr.Error())
} else {
fmt.Printf("未知错误:%v\n" , err)
}
return
}
if err := saveUser(username, email); err != nil {
if dbErr, ok := err.(*DatabaseError); ok {
fmt.Printf("数据库错误:%s\n" , dbErr.Error())
fmt.Printf("底层错误:%v\n" , dbErr.Err)
} else {
fmt.Printf("未知错误:%v\n" , err)
}
return
}
fmt.Println("用户保存成功" )
}
并发编程 Go 语言的并发模型是其最强大的特性之一,相比 Java 的线程模型更加轻量和高效。
Goroutine 基础 import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ThreadDemo {
public static void printNumbers (String threadName) {
for (int i = 1 ; i <= 5 ; i++) {
log.info("{}: {}" , threadName, i);
try {
Thread.sleep(100 );
} catch (InterruptedException e) {
log.error("线程被中断" , e);
Thread.currentThread().interrupt();
return ;
}
}
}
public static void main (String[] args) {
log.info("主线程开始" );
Thread thread1 = new Thread (() -> printNumbers("线程 1" ));
Thread thread2 = new Thread (() -> printNumbers("线程 2" ));
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
log.error("主线程被中断" , e);
}
log.info("主线程结束" );
}
}
package main
import (
"fmt"
"time"
)
func printNumbers (name string ) {
for i := 1 ; i <= 5 ; i++ {
fmt.Printf("%s: %d\n" , name, i)
time.Sleep(100 * time.Millisecond)
}
}
func main () {
fmt.Println("主线程开始" )
go printNumbers("Goroutine 1" )
go printNumbers("Goroutine 2" )
time.Sleep(1 * time.Second)
fmt.Println("主线程结束" )
}
Goroutine 启动成本远低于线程(KB 级内存 vs MB 级)
一个程序可以轻松创建数万甚至数十万 Goroutine
Goroutine 由 Go 运行时调度,而非操作系统
Goroutine 切换成本低,效率更高
同步与通信 Go 提倡 "不要通过共享内存来通信,而要通过通信来共享内存",这是与 Java 线程模型的重要区别。
使用 WaitGroup 等待 Goroutine package main
import (
"fmt"
"sync"
"time"
)
func worker (id int , wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("工作者 %d 开始工作\n" , id)
time.Sleep(time.Second)
fmt.Printf("工作者 %d 完成工作\n" , id)
}
func main () {
var wg sync.WaitGroup
for i := 1 ; i <= 5 ; i++ {
wg.Add(1 )
go worker(i, &wg)
}
wg.Wait()
fmt.Println("所有工作都已完成" )
}
通道(Channel)通信 package main
import "fmt"
func square (num int , ch chan int ) {
result := num * num
ch <- result
}
func main () {
results := make (chan int , 3 )
go square(2 , results)
go square(3 , results)
go square(4 , results)
res1 := <-results
res2 := <-results
res3 := <-results
fmt.Printf("2² = %d\n" , res1)
fmt.Printf("3² = %d\n" , res2)
fmt.Printf("4² = %d\n" , res3)
close (results)
}
带选择的通道操作 package main
import (
"fmt"
"time"
)
func produce (ch chan int , id int ) {
for i := 0 ; i < 3 ; i++ {
ch <- id*10 + i
time.Sleep(time.Millisecond * 500 )
}
close (ch)
}
func main () {
ch1 := make (chan int )
ch2 := make (chan int )
go produce(ch1, 1 )
go produce(ch2, 2 )
for {
select {
case num, ok := <-ch1:
if !ok {
ch1 = nil
break
}
fmt.Printf("从通道 1 接收:%d\n" , num)
case num, ok := <-ch2:
if !ok {
ch2 = nil
break
}
fmt.Printf("从通道 2 接收:%d\n" , num)
case <-time.After(1 * time.Second):
fmt.Println("超时" )
return
}
if ch1 == nil && ch2 == nil {
break
}
}
fmt.Println("所有数据接收完毕" )
}
互斥锁 虽然 Go 提倡使用通道通信,但在某些情况下仍需要使用互斥锁:
package main
import (
"fmt"
"sync"
"time"
)
type Counter struct {
value int
mu sync.Mutex
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) GetValue() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
func main () {
var counter Counter
var wg sync.WaitGroup
for i := 0 ; i < 10 ; i++ {
wg.Add(1 )
go func () {
defer wg.Done()
for j := 0 ; j < 1000 ; j++ {
counter.Increment()
time.Sleep(time.Nanosecond)
}
}()
}
wg.Wait()
fmt.Printf("最终计数:%d\n" , counter.GetValue())
}
包管理与依赖 Go 的包管理经历了多个阶段,目前(Go 1.11+)使用 go mod 作为官方包管理工具,类似于 Java 的 Maven 或 Gradle。
初始化项目
mkdir go-demo
cd go-demo
go mod init github.com/yourusername/go-demo
这会创建 go.mod 文件,类似于 pom.xml:
module github.com/yourusername/go-demo
go 1.22
添加依赖 module github.com/yourusername/go-demo
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
)
同时生成 go.sum 文件,记录依赖的校验和,确保依赖的完整性。
更新与移除依赖
go get -u github.com/gin-gonic/gin
go get github.com/gin-gonic/[email protected]
go mod tidy
实战示例:RESTful API 服务 下面我们实现一个完整的 RESTful API 服务,对比 Java Spring Boot 和 Go Gin 框架的实现方式。
Java Spring Boot 实现 <?xml version="1.0" encoding="UTF-8" ?>
<project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion > 4.0.0</modelVersion >
<parent >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-parent</artifactId >
<version > 3.2.5</version >
<relativePath />
</parent >
<groupId > com.example</groupId >
<artifactId > user-api</artifactId >
<version > 0.0.1-SNAPSHOT</version >
<name > user-api</name >
<description > User API with Spring Boot</description >
<properties >
<java.version > 17</java.version >
</properties >
<dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-data-jpa</artifactId >
</dependency >
<dependency >
<groupId > com.mysql</groupId >
<artifactId > mysql-connector-j</artifactId >
<scope > runtime</scope >
</dependency >
<dependency >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
<version > 1.18.30</version >
<scope > provided</scope >
</dependency >
<dependency >
<groupId > org.springdoc</groupId >
<artifactId > springdoc-openapi-starter-webmvc-ui</artifactId >
<version > 2.3.0</version >
</dependency >
<dependency >
<groupId > com.baomidou</groupId >
<artifactId > mybatis-plus-boot-starter</artifactId >
<version > 3.5.5</version >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
<scope > test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-maven-plugin</artifactId >
<configuration >
<excludes >
<exclude >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
</exclude >
</excludes >
</configuration >
</plugin >
</plugins >
</build >
</project >
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("users")
@Schema(description = "用户实体")
public class User {
@TableId(type = IdType.AUTO)
@Schema(description = "用户 ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "邮箱")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.userapi.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper <User> {
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.userapi.entity.User;
public interface UserService extends IService <User> {
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.userapi.entity.User;
import com.example.userapi.mapper.UserMapper;
import com.example.userapi.service.UserService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements UserService {
@Override
public boolean save (User user) {
if (user.getId() == null ) {
user.setCreatedAt(LocalDateTime.now());
}
user.setUpdatedAt(LocalDateTime.now());
return super .save(user);
}
}
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.userapi.entity.User;
import com.example.userapi.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "用户管理", description = "用户 CRUD 操作")
public class UserController {
private final UserService userService;
@Operation(summary = "获取所有用户")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功获取用户列表", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))})
})
@GetMapping
public ResponseEntity<List<User>> getAllUsers (
@Parameter(description = "用户名模糊查询")
@RequestParam(required = false) String username) {
log.info("获取用户列表,用户名筛选:{}" , username);
QueryWrapper<User> queryWrapper = new QueryWrapper <>();
if (StringUtils.hasText(username)) {
queryWrapper.like("username" , username);
}
List<User> users = userService.list(queryWrapper);
return ResponseEntity.ok(users);
}
@Operation(summary = "根据 ID 获取用户")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "找到用户", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))}),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
@GetMapping("/{id}")
public ResponseEntity<User> getUserById (
@Parameter(description = "用户 ID")
@PathVariable Long id) {
log.info("获取用户,ID: {}" , id);
return userService.getById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@Operation(summary = "创建新用户")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "用户创建成功", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))}),
@ApiResponse(responseCode = "400", description = "无效的请求数据")
})
@PostMapping
public ResponseEntity<User> createUser (
@Parameter(description = "用户信息")
@RequestBody User user) {
log.info("创建用户:{}" , user.getUsername());
if (!StringUtils.hasText(user.getUsername()) || !StringUtils.hasText(user.getEmail())) {
return ResponseEntity.badRequest().build();
}
userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@Operation(summary = "更新用户信息")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "用户更新成功", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = User.class))}),
@ApiResponse(responseCode = "400", description = "无效的请求数据"),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
@PutMapping("/{id}")
public ResponseEntity<User> updateUser (
@Parameter(description = "用户 ID")
@PathVariable Long id,
@Parameter(description = "更新的用户信息")
@RequestBody User user) {
log.info("更新用户,ID: {}" , id);
if (!userService.existsById(id)) {
return ResponseEntity.notFound().build();
}
if (!StringUtils.hasText(user.getUsername()) || !StringUtils.hasText(user.getEmail())) {
return ResponseEntity.badRequest().build();
}
user.setId(id);
userService.save(user);
return ResponseEntity.ok(user);
}
@Operation(summary = "删除用户")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "用户删除成功"),
@ApiResponse(responseCode = "404", description = "用户不存在")
})
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser (
@Parameter(description = "用户 ID")
@PathVariable Long id) {
log.info("删除用户,ID: {}" , id);
if (!userService.existsById(id)) {
return ResponseEntity.notFound().build();
}
userService.removeById(id);
return ResponseEntity.noContent().build();
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserApiApplication {
public static void main (String[] args) {
SpringApplication.run(UserApiApplication.class, args);
}
}
配置文件(application.properties):
# 服务器配置
server.port=8080
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath:mapper/*.xml
# Swagger 配置
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui.html
Go Gin 实现 package model
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primarykey" json:"id"`
Username string `gorm:"size:50;not null" json:"username"`
Email string `gorm:"size:100;uniqueIndex;not null" json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
package db
import (
"fmt"
"github.com/yourusername/go-user-api/model"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
var DB *gorm.DB
func InitDB () {
username := "root"
password := "password"
host := "localhost"
port := "3306"
dbName := "user_db"
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local" , username, password, host, port, dbName)
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalf("数据库连接失败:%v" , err)
}
err = DB.AutoMigrate(&model.User{})
if err != nil {
log.Fatalf("数据表迁移失败:%v" , err)
}
log.Println("数据库连接成功" )
}
控制器(handler/user_handler.go):
package handler
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/yourusername/go-user-api/db"
"github.com/yourusername/go-user-api/model"
)
func GetAllUsers (c *gin.Context) {
username := c.Query("username" )
var users []model.User
tx := db.DB
if username != "" {
tx = tx.Where("username LIKE ?" , "%" +username+"%" )
}
result := tx.Find(&users)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error" : result.Error.Error()})
return
}
c.JSON(http.StatusOK, users)
}
func GetUserById (c *gin.Context) {
var user model.User
id := c.Param("id" )
result := db.DB.First(&user, id)
if result.Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error" : "用户不存在" })
return
}
c.JSON(http.StatusOK, user)
}
func CreateUser (c *gin.Context) {
var user model.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error" : err.Error()})
return
}
if user.Username == "" || user.Email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error" : "用户名和邮箱不能为空" })
return
}
result := db.DB.Create(&user)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error" : result.Error.Error()})
return
}
c.JSON(http.StatusCreated, user)
}
func UpdateUser (c *gin.Context) {
id := c.Param("id" )
var user model.User
if result := db.DB.First(&user, id); result.Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error" : "用户不存在" })
return
}
var updateUser model.User
if err := c.ShouldBindJSON(&updateUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error" : err.Error()})
return
}
if updateUser.Username == "" || updateUser.Email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error" : "用户名和邮箱不能为空" })
return
}
user.Username = updateUser.Username
user.Email = updateUser.Email
result := db.DB.Save(&user)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error" : result.Error.Error()})
return
}
c.JSON(http.StatusOK, user)
}
func DeleteUser (c *gin.Context) {
id := c.Param("id" )
var user model.User
if result := db.DB.First(&user, id); result.Error != nil {
c.JSON(http.StatusNotFound, gin.H{"error" : "用户不存在" })
return
}
result := db.DB.Delete(&user)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error" : result.Error.Error()})
return
}
c.Status(http.StatusNoContent)
}
package router
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/files"
_ "github.com/yourusername/go-user-api/docs"
"github.com/yourusername/go-user-api/handler"
)
func SetupRouter () *gin.Engine {
r := gin.Default()
r.GET("/swagger/*any" , ginSwagger.WrapHandler(swaggerFiles.Handler))
api := r.Group("/api" )
{
users := api.Group("/users" )
{
users.GET("" , handler.GetAllUsers)
users.GET("/:id" , handler.GetUserById)
users.POST("" , handler.CreateUser)
users.PUT("/:id" , handler.UpdateUser)
users.DELETE("/:id" , handler.DeleteUser)
}
}
return r
}
package main
import (
"log"
"github.com/yourusername/go-user-api/db"
"github.com/yourusername/go-user-api/router"
)
func main () {
db.InitDB()
r := router.SetupRouter()
log.Println("服务器启动在 :8080 端口" )
if err := r.Run(":8080" ); err != nil {
log.Fatalf("服务器启动失败:%v" , err)
}
}
总结与进阶学习 通过本文,我们对比学习了 Go 语言与 Java 的核心差异,包括:
语法简洁性 :Go 语法更简洁,去除了 Java 中的许多仪式性代码
并发模型 :Go 的 Goroutine 和通道模型比 Java 的线程模型更轻量高效
错误处理 :Go 采用返回值方式处理错误,Java 使用异常机制
面向对象 :Go 通过结构体和接口实现类似 OOP 的功能,没有类和继承
包管理 :Go 使用 go mod,Java 常用 Maven/Gradle
学习资源 作为 Java 开发者,掌握 Go 语言将为你打开云原生开发的大门,无论是容器编排、服务网格还是高性能微服务,Go 都展现出独特优势。通过不断实践,你将能够灵活选择最适合的工具解决实际问题,成为一名更全面的后端开发者。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online