Java 开发者的 Go 语法基础:从 0 开始快速上手 Go

一、为什么 Java 开发者学 Go 会觉得"简单又陌生"?

Java 和 Go 都是静态类型、编译型语言,都支持并发、接口和垃圾回收。因此,Java 开发者学习 Go 的难点通常不是看不懂代码,而是要适应几种不同的设计:

  1. Go 没有 class、继承和构造器,更多使用 struct、组合和接口。
  2. Go 强调显式处理错误,不使用 Java 那样的受检异常体系。
  3. Go 的并发是语言级能力,goroutinechannel 比创建线程更轻量。
  4. Go 的代码格式、未使用变量、未使用 import 都由工具严格约束。

可以先记住一句话:

Java 更强调面向对象的类型体系,Go 更强调组合、接口和简单直接。

二、先写出第一个 Go 程序

1. Java 与 Go 的 Hello World

Java:

java 复制代码
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Go");
    }
}

Go:

Go 复制代码
package main

import "fmt"

func main() {
    fmt.Println("Hello, Go")
}

package main 表示这是一个可执行程序,func main() 是程序入口,fmt.Println 相当于 Java 中的 System.out.println

2. 运行 Go 程序

安装 Go 后,可以直接运行:

复制代码
go run main.go

初始化项目:

复制代码
go mod init example.com/hello
go build

Go 使用 go.mod 管理模块和依赖,定位上类似 Maven 的 pom.xml,但文件更加简洁。

三、变量、常量与基本类型

1. 变量声明

Java:

java 复制代码
int age = 18;
String name = "Tom";

Go 有两种常用写法:

Go 复制代码
var age int = 18
var name string = "Tom"

// 编译器自动推断类型
score := 95

**:=**同时完成变量声明和赋值,但只能在函数内部使用。

例如:

Go 复制代码
func main() {
    name := "Tom"
    age := 18

    fmt.Println(name, age)
}

Go 要求局部变量必须使用,否则会编译失败。这可以避免很多无用代码遗留在项目中。

2. 零值

Go 声明变量后会自动获得零值:

复制代码
var i int       // 0
var ok bool     // false
var text string // ""
var p *int      // nil

Java 中对象引用默认是 null,而 Go 对不同类型都有明确的零值。

3. 常量

Go 复制代码
const Pi = 3.1415926
const AppName string = "demo"

Go 的常量在编译期确定,不能重新赋值,使用方式类似 Java 的 static final

4. 常用类型对照

Java Go 说明
byte byte / uint8 8 位无符号整数
int int 整数,位数与平台相关
long int64 64 位整数
float float32 32 位浮点数
double float64 64 位浮点数
boolean bool 布尔值
String string UTF-8 字节序列
Object any interface{} 的别名

Go 不支持隐式数值类型转换:

复制代码
var age int = 18
var total int64 = int64(age)

intint64 之间必须显式转换。


四、流程控制:Go 只有一个循环关键字

1. if 语句

Java:

java 复制代码
if (age >= 18) {
    System.out.println("adult");
}

Go:

Go 复制代码
if age >= 18 {
    fmt.Println("adult")
}

Go 的条件表达式不需要括号,但大括号不能省略。

还可以在条件前声明临时变量:

Go 复制代码
//获取字符串 name 的长度,并赋值给变量 n
//判断n长度是否大于0
if n := len(name); n > 0 {
    fmt.Println("字符串长度:", n)
}

2. for 循环

Go 没有 whiledo...while,统一使用 for

普通循环:

Go 复制代码
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

相当于 Java 中的 while

java 复制代码
for count < 10 {
    count++
}

无限循环:

Go 复制代码
for {
    fmt.Println("持续执行")
    break
}

3. switch 语句

Go 的 switch 默认不会自动贯穿,因此通常不需要写 break

Go 复制代码
switch day {
case 1:
    fmt.Println("Monday")
case 6, 7:
    fmt.Println("Weekend")
default:
    fmt.Println("Weekday")
}

多个条件可以写在同一个 case 中:

Go 复制代码
case 6, 7:
    fmt.Println("周末")

Go 还支持不写表达式的 switch

Go 复制代码
switch {
case score >= 90:
    fmt.Println("A")
case score >= 60:
    fmt.Println("B")
default:
    fmt.Println("C")
}

这种写法类似 Java 中的多组 if...else if


五、函数:Go 支持多个返回值

1. 基本函数写法

Go 复制代码
// func  方法名称(参数类型)返回值
func add(a int, b int) int {
    return a + b
}

如果多个参数类型相同,可以简写:

Go 复制代码
func add(a, b int) int {
    return a + b
}

调用函数:

Go 复制代码
//相当于 var int result;
//result = add(10,20)
result := add(10, 20)
fmt.Println(result)

2. 多返回值

这是 Java 开发者需要重点适应的语法:

Go 复制代码
//返回float64,和erroe
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为 0")
    }
    
    //对应返回
    return a / b, nil
}

调用:

复制代码
result, err := divide(10, 2)

if err != nil {
    fmt.Println("计算失败:", err)
    return
}

fmt.Println(result)

Java 通常需要返回一个对象、使用异常,或者通过其他方式传递多个结果;Go 习惯把结果和错误一起返回。

3. 可变参数

Go 复制代码
//nums ...int 表示:可以接收 0 个或多个 int 类型的参数
func sum(nums ...int) int {
    total := 0
    //_ 是索引,这里用不上就写_这个
    for _, n := range nums {
        total += n
    }

    return total
}

调用:

Go 复制代码
result := sum(1, 2, 3, 4)
fmt.Println(result)

4. defer

defer 表示函数结束时执行某段代码:

Go 复制代码
func demo() {
    defer fmt.Println("函数结束时执行")

    fmt.Println("执行主要逻辑")
}

defer 常用于关闭文件、释放连接 等操作,使用场景类似 Java 中的 finally

多个 defer 的执行顺序是后进先出:

Go 复制代码
func demo() {
    defer fmt.Println("第一个 defer")
    defer fmt.Println("第二个 defer")
}

执行结果:

Go 复制代码
第二个 defer
第一个 defer

六、数组、切片和 map

1. 数组

Go 的数组长度是类型的一部分:

Go 复制代码
var arr [3]int = [3]int{1, 2, 3}

[3]int[4]int 是两种不同的类型。

2. 切片

实际开发中,Go 更常使用切片:

Go 复制代码
nums := []int{1, 2, 3}

//append是GO的内置函数,而不需要像java一样,需要xx.append()
nums = append(nums, 4)

fmt.Println(nums)
//左闭右开,取1、2索引
fmt.Println(nums[1:3])

输出:

Go 复制代码
[1 2 3 4]
[2 3]

切片可以理解成一个动态数组,使用体验类似 Java 中的 ArrayList,但它不是一个类。

创建指定长度的切片:

Go 复制代码
//创建一个长度为3的int切片
// make([]int, 3) 会分配一个底层数组,包含 3 个元素,值默认为0
nums := make([]int, 3)
nums[0] = 10

3. map

Go 中的 map 类似 Java 的 HashMap

Go 复制代码
ages := map[string]int{
    "Tom":  18,
    "Lucy": 20,
}

新增或修改元素:

Go 复制代码
ages["Tom"] = 19

读取元素:

Go 复制代码
// 返回两个值:
//   - age: 键对应的值(如果存在),否则为值类型的零值
//   - exists: bool 类型,true 表示键存在,false 表示不存在
age, exists := ages["Tom"]

if exists {
    fmt.Println("Tom 的年龄:", age)
}

删除元素:

Go 复制代码
delete(ages, "Lucy")

也可以使用 make 创建 map:

Go 复制代码
//初始化map
scores := make(map[string]int)
scores["Go"] = 100

需要注意,直接向 nil map 写入会触发 panic(程序崩溃):

Go 复制代码
//var 声明的 map 默认值是 nil,它只声明了变量,但没有分配内存空间
// scores 是 nil(空指针),没有初始化
var scores map[string]int

// 错误:会 panic
scores["Go"] = 100

//所以,要么用make进行初始化
//要么scores := map[string]int{}

但是从 nil map 中读取数据是安全的。

4. range 遍历

遍历切片:

Go 复制代码
for index, value := range nums {
    fmt.Println(index, value)
}

遍历 map:

Go 复制代码
for key, value := range ages {
    fmt.Println(key, value)
}

如果不需要某个返回值,可以使用下划线**_**忽略:

Go 复制代码
for _, value := range nums {
    fmt.Println(value)
}

七、struct、方法与指针

Go 没有 Java 中的 class,但可以使用 struct 组织数据。

1. 定义 struct

Go 复制代码
type User struct {
    Name string
    Age  int
}

创建对象:

Go 复制代码
user := User{
    Name: "Tom",
    Age:  18,
}

fmt.Println(user.Name)

也可以使用位置初始化:

Go 复制代码
user := User{"Tom", 18}

不过实际项目中更推荐使用字段名初始化,这样可读性更好。

2. 模拟构造器

Go 没有构造器关键字,通常使用普通函数创建对象:

Go 复制代码
func NewUser(name string, age int) User {
    return User{
        Name: name,
        Age:  age,
    }
}

调用:

Go 复制代码
user := NewUser("Tom", 18)

3. 定义方法

Go 复制代码
//定义Intro方法名,接收参数为User,返回参数为String
func (u User) Intro() string {
    // Sprintf 格式化字符串,但不输出到控制台,而是返回
    return fmt.Sprintf("我叫 %s,今年 %d 岁", u.Name, u.Age)
}

调用:

Go 复制代码
user := User{Name: "Tom", Age: 18}
fmt.Println(user.Intro())

4. 指针接收者

Go 复制代码
func (u *User) Birthday() {
    //如果有指针,说明这个值是会影响原来的对象
    u.Age++
}

调用:

Go 复制代码
user := User{Name: "Tom", Age: 18}
user.Birthday()

fmt.Println(user.Age) // 19

User 是值接收者,方法拿到的是对象副本;*User 是指针接收者,可以修改原对象。

Go 会自动完成取地址和解引用,因此通常可以直接写:

Go 复制代码
user.Birthday()

而不需要手动写 &user

5. Go 的可见性规则

Go 不使用 publicprivateprotected

标识符首字母大写,表示对其他包可见;首字母小写,表示包内私有。

Go 复制代码
type User struct {
    Name string // 对外可见
    age  int    // 包内私有
}

八、接口:Go 的实现是隐式的

Java 中实现接口需要显式写 implements

java 复制代码
class Dog implements Animal {
    // ...
}

Go 不需要显式声明实现接口:

Go 复制代码
type Speaker interface {
    Speak() string
}

type Dog struct{}

func (Dog) Speak() string {
    return "wang"
}

定义一个接收接口参数的函数:

Go 复制代码
func say(s Speaker) {
    fmt.Println(s.Speak())
}

调用:

Go 复制代码
dog := Dog{}
say(dog)

只要 Dog 拥有接口要求的全部方法,它就自动实现了 Speaker

这和 Java 最大的区别是:

Java 是"声明我实现了哪个接口",Go 是"只要方法匹配,就自动实现接口"。

Go 的接口通常比较小,例如:

Go 复制代码
type Reader interface {
    Read(p []byte) (n int, err error)
}

小接口有利于降低耦合,让代码更容易测试和扩展。


九、错误处理:if err != nil 是日常代码

Go 通常把可预期的失败作为普通返回值:

Go 复制代码
//读配置文件,返回data和err
data, err := os.ReadFile("config.json")

//如果err不为空,则返回失败
if err != nil {
    return fmt.Errorf("读取配置失败: %w", err)
}

fmt.Println(string(data))

Go 不鼓励通过异常处理普通业务错误,而是显式判断:

Go 复制代码
result, err := doSomething()

if err != nil {
    return err
}

常见经验:

  • 能恢复、能提示用户的失败,使用 error 返回。
  • 真正不可恢复的程序错误,才考虑使用 panic
  • 不要为了少写几行 if 而滥用 panic
  • 可以使用 %w 包装错误,保留原始错误信息。

例如:

Go 复制代码
return fmt.Errorf("查询用户失败: %w", err)

十、goroutine 与 channel:Go 的并发入门

Java 中常见的并发方式是创建线程或使用线程池;Go 可以使用 go 关键字启动一个 goroutine:

Go 复制代码
func work() {
    fmt.Println("执行任务")
}

func main() {
    go work()

    time.Sleep(time.Second)
}

上面的代码只是演示语法,生产环境不建议使用 Sleep 等待任务完成。

1. 使用 channel 通信

复制代码
func main() {
    done := make(chan string)

    go func() {
        done <- "任务完成"
    }()

    message := <-done
    fmt.Println(message)
}

done <- "任务完成" 表示向 channel 发送数据。

message := <-done 表示从 channel 接收数据。

channel 可以用于 goroutine 之间传递数据和同步。可以把它理解为一种类型安全的并发队列,但它不仅能传递数据,也能表达任务是否完成。

2. 使用 WaitGroup

复制代码
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(1)

    go func() {
        defer wg.Done()
        fmt.Println("任务执行完成")
    }()

    wg.Wait()
}

WaitGroup 类似于等待多个线程执行完成。


十一、Java 开发者最容易踩的坑

1. 分号不是必须的

Go 会自动插入分号,通常不要手写:

Go 复制代码
fmt.Println("Hello")

2. 大括号不能单独换行

推荐写法:

Go 复制代码
if age > 18 {
    fmt.Println("adult")
}

不要写成:

Go 复制代码
if age > 18
{
    fmt.Println("adult")
}

3. 未使用变量会导致编译失败

Go 复制代码
func main() {
    name := "Tom"
}

上面的代码无法编译,因为 name 声明后没有使用。

4. 未使用 import 也会导致编译失败

复制代码
import (
    "fmt"
    "os"
)

如果代码没有使用 os,程序同样无法编译。

5. 字符串不是字符数组

Go 的字符串底层是 UTF-8 字节序列:

Go 复制代码
text := "你好"

fmt.Println(len(text))

len(text) 返回的是字节数,不是字符数。

如果需要按 Unicode 字符遍历:

Go 复制代码
for _, r := range text {
    fmt.Println(r)
}

6. range 中的 value 是副本

Go 复制代码
nums := []int{1, 2, 3}

for _, value := range nums {
    value = value * 2
}

fmt.Println(nums) // [1 2 3]

因为 value 是元素副本,所以不会修改原切片。

正确写法:

Go 复制代码
for i := range nums {
    nums[i] = nums[i] * 2
}

7. 不要把 Go 当成"没有 class 的 Java"

Go 的设计重点不是模拟 Java 的继承体系,而是:

  • 使用 struct 表示数据
  • 使用方法表示行为
  • 使用组合代替继承
  • 使用小接口降低耦合
  • 使用函数和错误返回值简化流程

十二、Java 与 Go 常用语法对照

功能 Java Go
程序入口 public static void main func main()
输出 System.out.println() fmt.Println()
字符串 String string
动态数组 ArrayList slice
哈希表 HashMap map
class struct
继承 extends 不支持,使用组合
接口实现 implements 隐式实现
异常处理 try-catch 返回 error
延迟执行 finally defer
线程 Thread、线程池 goroutine
并发通信 阻塞队列等 channel
包管理 Maven、Gradle Go Modules

十三、建议的学习顺序

可以按照下面的顺序学习 Go,并尝试实现一个命令行 Todo 项目:

  1. 变量、基础类型、ifforswitch
  2. 函数、多返回值、errordefer
  3. slice、map、struct 和方法
  4. 包、go.mod、文件读写和 JSON
  5. 接口、单元测试
  6. goroutine、channel 和并发控制
  7. HTTP 服务、数据库操作和项目实战

对于 Java 开发者来说,最好的学习方式不是只看语法,而是选择一个熟悉的小项目,用 Go 重新实现一遍。

例如:

  • 用 Go 实现一个 Todo 命令行工具
  • 用 Go 实现一个简单 HTTP 接口
  • 用 Go 读取 JSON 文件并保存到数据库
  • 用 Go 编写一个并发任务处理器

总结

对于 Java 开发者来说,Go 的语法并不复杂,真正需要改变的是编程思维:

  • 少一些继承层次,多一些组合
  • 少一些异常跳转,多一些显式错误
  • 少一些重量级线程管理,多一些 goroutine 和 channel
  • 少一些复杂框架配置,多一些简单直接的代码

当你掌握以下五组核心语法后,就已经具备阅读大多数 Go 项目的基础:

复制代码
struct + method + interface + error + goroutine

接下来可以选择一个熟悉的 Java 小项目,用 Go 重新实现一遍。相比单纯背诵语法,这种方式更容易真正掌握 Go。

相关推荐
LccKyI1 小时前
C#学习day05(开发福彩双色球系统附思维导图)
开发语言·学习·c#
zzz_23681 小时前
TencentDB-Agent-Memory 深度解析:让多个 Agent 共享项目经验的记忆中枢
java·开发语言·jvm·人工智能·agent·memory·tencent db
vx-程序开发2 小时前
django医院预约挂号系统---附源码23353
java·javascript·spring boot·python·eclipse·django·php
金斗潼关2 小时前
ysoserial的使用
java
ruleslol2 小时前
volatile 关键字
java
charlie1145141912 小时前
IMX6ULL WM8960 的移植——前置介绍
开发语言·c++·开源项目·嵌入式linux
Codelinghu2 小时前
AI 写代码越强,程序员越不能只懂代码
后端
迷迭香yy2 小时前
Python实战:涨停板“假封板”识别系统的工程实现
开发语言·人工智能·python
卡卡敲码2 小时前
Skills 撞车了,Agent 怎么选
后端