先说下感受:Java和Go都是OOP,师出同源,只是在语法上有些不同而已,只要不着相,学起来很快的。用Go真的可以少写很多代码,舒服!!
Go是约定大于配置的忠实实践者!!!
为什么有这篇?

怎么学?
我选B。
然后就把之前学习过程的资源整理一下,发出来,分享一下。
对Java开发者来说,Go的核心魅力是「极简 + 高效」—— 舍弃冗余语法糖,用最基础的构件实现面向对象、并发编程的核心能力。本文直击Java与Go的核心对应关系,从语法到并发,一步到位掌握关键要点。

国内访问更稳定的镜像站是 https://golang.google.cn/。
go version验证(类比java -version);



package main // 程序入口包(固定)
import "fmt" // 类比java.lang.System
func main() { // 入口函数(无参数、无返回值)
fmt.Println("Hello Go") // 类比System.out.println
}核心差异:Go 函数可直接定义在包下,无需像 Java 那样用类包裹。
Go 的入口方法规则:必须是main包下的main方法 —— 这是 Go 的强制规范:
程序的唯一入口是main包中定义的func main()(无参数、无返回值);
其他包内的main函数不会被识别为程序入口(类比 Java:只有包含public static void main(String[] args)的类能作为入口,且一个程序仅能有一个入口)。
Java 写法 | Go 写法(推荐) | 关键说明 |
|---|---|---|
int i = 10; | i := 10 | :=自动推导类型,函数内优先用 |
final int d = 60; | const d = 60 | 编译期常量,无类型推导也可 |
实战代码:
func main() {
i := 10 // 简洁声明(推荐)
j := 20
str := "hello go" // 自动推导string类型
const d = 60 // 常量
fmt.Println(i+j, str)
}Java 类特性 | Go 实现方式 |
|---|---|
class 定义 | type 结构体名 struct {} |
this 引用 | 指针接收者 (u *User) |
private 字段 | 小写字段(age) |
public 方法 | 大写方法(SetAge/GetName) |
toString() | 实现 fmt.Stringer 接口 |
type User struct {
ID int // 包外公有(类似public)
Name string // 包外公有
age int // 包内私有(类似private)
}
// 指针接收者:修改原对象+避免拷贝(类比Java this)
func (u *User) SetAge(newAge int) {
u.age = newAge
}
// 指针接收者:统一行为,避免冗余
func (u *User) GetName() string {
return u.Name
}
// 类比Java重写toString(),自定义打印格式
func (u *User) String() string {
return fmt.Sprintf("User{ID:%d, Name:%q, Age:%d}", u.ID, u.Name, u.age)
}
// 类比Java构造方法
func NewUser(id int, name string, age int) *User {
return &User{ID: id, Name: name, age: age}
}func main() {
// 切片(ArrayList):动态扩容,append返回新切片
s := []int{1,2,3}
s = append(s, 4,5) // 类比list.add()
for idx, val := range s { // 遍历:索引+值
fmt.Println("idx:", idx, "val:", val)
}
// 字典(HashMap):无序,安全取值(ok判断是否存在)
m := make(map[string]int)
m["a"] = 1
v, ok := m["a"] // 类比map.get(),避免空指针
fmt.Println(v, ok) // 1 true
delete(m, "a") // 类比map.remove()
}Java 需显式implements,Go 只要方法签名匹配,自动实现接口(解耦核心):
// 定义接口(仅声明方法)
type SayHello interface {
Hello() string
}
// User无需声明implements,有Hello方法即实现接口
func (u *User) Hello() string {
return "Hello " + u.Name
}
// 接收接口参数(多态,类比Java接口入参)
func Greet(s SayHello) {
fmt.Println(s.Hello())
}
// 调用
func main() {
u := NewUser(1, "张三", 30)
Greet(u) // 输出:Hello 张三
}什么“鸭子类型”?只要实现了接口的所有方法,就隐式实现该接口(无需显式使用关键字implements)。
鸭子类型的核心哲学来自一句美国谚语: “如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。” 翻译成编程术语: 一个对象的“类型”不是由它的继承关系/显式声明决定的,而是由它“能做什么”(拥有的方法)决定的。 换句话说:只要一个类型实现了某个接口要求的所有方法,它就 “是” 这个接口类型,无需任何显式关联声明。 民族文化
Java 线程占 1MB 栈,Go 协程仅 2KB,百万级协程无压力:
// 启动协程:仅需go关键字(类比new Thread().start())
go printNum(10)Java 习惯用 static 变量,Go 推荐局部WaitGroup+ 指针传递(减少耦合):
func main() {
var wg sync.WaitGroup // 局部变量(类比CountDownLatch)
wg.Add(2) // 计数2
// 传指针:避免值拷贝,协程内操作原对象
go printNum(10, &wg)
go printNum(20, &wg)
wg.Wait() // 等待计数为0(类比latch.await())
}
func printNum(n int, wg *sync.WaitGroup) {
defer wg.Done() // 类比countDown(),确保执行
for i := 0; i < n; i++ {
fmt.Print(n, "-", i, " ")
time.Sleep(100 * time.Millisecond)
}
}Java 用volatile+synchronized或ConcurrentHashMap实现线程通信,Go 用 Channel(管道)实现协程通信,遵循 “通过通信共享内存,而非共享内存通信” 的原则。
类比 Java BlockingQueue,发送端必须关闭通道,避免接收端死锁:
func main() {
// 带缓冲通道(类比ArrayBlockingQueue)
ch := make(chan string, 3)
ch <- "a"
ch <- "b"
ch <- "c"
close(ch) // 发送端关闭!否则for range死锁
// 遍历通道:接收所有数据直到关闭
for msg := range ch {
fmt.Println("接收:", msg)
}
}答:不会。Java 线程默认栈内存 1MB,而 Go 协程初始栈仅 2KB,且支持动态扩容(最大 1GB)。Go 的调度器会将协程映射到系统线程(M:N 调度),百万级协程也不会导致资源耗尽,而 Java 线程超过千级就会 OOM。
答:带缓冲 Channel 的发送操作会阻塞,直到有接收者取走数据。
封装私有字段,用defer确保解锁(类比 Java finally):
type Counter struct {
mu sync.Mutex // 互斥锁(类比ReentrantLock)
count int // 私有字段,避免包外直接修改
}
// 并发安全自增
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock() // 确保解锁,避免死锁
c.count++
}
// 暴露读取方法(类比getter)
func (c *Counter) GetCount() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
// 调用
func main() {
counter := &Counter{}
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Inc()
}()
}
wg.Wait()
fmt.Println("最终计数:", counter.GetCount()) // 3
}defer为什么能保证解锁?答:defer是 Go 的延迟执行关键字,即使方法内触发panic(类似 Java 的 RuntimeException),defer后的代码也会执行。如果直接把unlock写在方法最后,panic会导致解锁失败,引发死锁。defer+ 锁是 Go 的标准写法。一句代码前加了defer,就相当于这句代码被放到java的finally方法块中了。
Java 用try-catch-finally处理异常,Go 用panic(触发异常)+recover(捕获异常)+defer(兜底),语法更简洁。
public static void main(String[] args) {
try {
int a = 1 / 0; // 触发ArithmeticException
} catch (ArithmeticException e) {
e.printStackTrace(); // 捕获异常
} finally {
System.out.println("无论是否异常都执行");
}
}func divide(a, b int) {
defer func() {
// 捕获panic(必须在defer中)
if err := recover(); err != nil {
fmt.Println("捕获异常:", err)
}
}()
if b == 0 {
panic("除数不能为0") // 触发异常
}
fmt.Println("结果:", a/b)
}
func main() {
divide(1, 0)
fmt.Println("程序继续运行") // 异常被捕获,不会崩溃
}常见疑惑:panic和 Java 的异常有什么区别?答:panic是 “致命异常”,若不捕获会导致程序崩溃(类似 Java 的 Error),而 Java 的 Exception 可选择性捕获。Go 的设计哲学是 “少用异常,多用返回值”—— 普通错误(如参数错误)用error类型返回,只有不可恢复的错误(如除数为 0)才用panic。
Java 用 Maven/Gradle 管理依赖,Go 用go mod(Go 1.11 + 标配),无需像传统 GOPATH 那样约束项目路径,更灵活。
需求 | Go mod 命令 | Maven 对应操作 |
|---|---|---|
初始化项目 | go mod init github.com/yourname/yourproject | mvn archetype:generate(创建项目) |
添加依赖 | go get github.com/gin-gonic/gin@v1.9.1 | 配置 pom.xml 后mvn install |
同步依赖(下载 / 清理) | go mod tidy(生成 go.sum) | mvn clean compile(下载依赖) |
编译运行 | go run main.go | mvn exec:java |
编译为二进制 | go build -o myapp main.go | mvn package(生成 jar) |
Gin 是 Go 的主流 Web 框架,类似 Java 的 Spring Boot,快速体验 Go 的 Web 开发:
1. 初始化项目:
mkdir gin-demo && cd gin-demo
go mod init github.com/yourname/gin-demo // yourname可以改为项目名,譬如gin-demo2. 添加 Gin 依赖:
go get github.com/gin-gonic/gin@v1.9.13. 编写代码(main.go):
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 初始化Gin引擎
// 定义接口(类似Spring的@RequestMapping)
r.GET("/hello", func(c *gin.Context) {
name := c.Query("name") // 获取请求参数
c.JSON(200, gin.H{ // 返回JSON
"message": "Hello " + name,
})
})
r.Run(":8080") // 启动服务(类似Spring的run方法)
}4. 运行与访问:
go run main.go
curl http://localhost:8080/hello?name=Go // 输出{"message":"Hello Go"}go.sum是什么?答:go.sum是依赖哈希校验文件,记录每个依赖的哈希值,确保下载的依赖和官方一致,防止篡改(类似 Maven 的 sha1 校验)。提交代码时需将go.mod和go.sum一起提交,其他人克隆后执行go mod tidy即可复现依赖。
“github.com/yourname/gin-demo“中的”yourname“是需要自定义一下吗?还是有特殊含义?
Go 模块路径 | Maven 依赖坐标 | 对应关系 |
|---|---|---|
github.com/yourname/gin-demo | groupId:com.younameartifactId: gin-demo | yourname → groupId 的核心(命名空间)gin-demo → artifactId(项目名) |
简单说:yourname 就是你的 “专属命名空间”,只要能唯一标识你的项目,怎么填都可以 —— 核心是「自定义」,而非「固定值」。
Go 语言模块路径采用 github.com 作为前缀并非官方强制要求,而是 Go 生态长期形成的「约定俗成」,核心源于历史习惯、生态协同和工程实践的便利性。
https://github.com/helloworldtang/java2go
package main
import (
"fmt"
"sync"
"time"
)
// 1. 结构体(对应Java的类)
type User struct {
ID int
Name string
age int // 小写包内私有,类比Java的private
}
// SetAge 设置年龄(指针接收者修改原对象,类比Java的this)
func (u *User) SetAge(newAge int) {
u.age = newAge
}
// GetName 获取姓名(统一指针接收者,保持行为一致)
func (u *User) GetName() string {
return u.Name
}
// String 实现fmt.Stringer接口,类比Java重写toString()
func (u *User) String() string {
return fmt.Sprintf("User{ID:%d, Name:%q, Age:%d}", u.ID, u.Name, u.age)
}
// NewUser 构造函数,类比Java的构造方法
func NewUser(id int, name string, age int) *User {
return &User{
ID: id,
Name: name,
age: age,
}
}
// 2. 接口(鸭子类型,无需显式implements)
type SayHello interface {
Hello() string
}
// Hello 隐式实现SayHello接口
func (u *User) Hello() string {
return "Hello " + u.Name
}
// Greet 接收接口参数,实现多态,类比Java接口入参
func Greet(s SayHello) {
fmt.Println(s.Hello())
}
// 3. 并发安全计数器(类比Java线程安全类)
type Counter struct {
mu sync.Mutex // 互斥锁,类比ReentrantLock
count int // 私有字段,避免包外直接修改
}
// Inc 并发安全自增
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock() // 延迟解锁,类比Java的finally
c.count++
}
// GetCount 安全获取计数(暴露getter方法)
func (c *Counter) GetCount() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
// 4. 协程函数(类比Java线程任务)
func printNum(n int, wg *sync.WaitGroup) {
defer wg.Done() // 确保协程结束标记,类比CountDownLatch.countDown()
for i := 0; i < n; i++ {
fmt.Print(n, "-", i, " ")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// 变量声明:优先用:=,自动推导类型
i := 10
fmt.Println("变量:", i)
// 结构体使用
u := NewUser(1, "张三", 30)
u.SetAge(35)
fmt.Println("User:", u)
Greet(u)
// 切片(类比Java ArrayList)和字典(类比Java HashMap)
s := []int{1, 2, 3}
s = append(s, 4) // 动态扩容
m := map[string]int{"a": 1}
delete(m, "a") // 删除元素(不存在不报错)
fmt.Println("切片:", s, "字典:", m)
// 协程+WaitGroup(类比Java线程+CountDownLatch)
var wg sync.WaitGroup
wg.Add(2)
go printNum(10, &wg)
go printNum(20, &wg)
wg.Wait() // 等待所有协程完成
// Channel(类比Java BlockingQueue)
ch := make(chan string, 3)
ch <- "a"
ch <- "b"
close(ch) // 发送端关闭,避免接收端死锁
for msg := range ch {
fmt.Println("Channel:", msg)
}
// 并发计数器使用
counter := &Counter{}
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
defer wg.Done()
counter.Inc()
}()
}
wg.Wait()
fmt.Println("最终计数:", counter.GetCount())
}type User struct { BaseInfo }组合复用代码(避免 Java 继承层级爆炸);go run调试代码。pprof工具排查并发问题(类似 Java 的 jstack)。go mod依赖管理。Java 转 Go,本质是「放下语法糖,直击本质」:
结构体+指针接收者替代类和 this;鸭子类型接口替代侵入式接口;协程+Channel替代线程 + 锁;:=+defer让代码更简洁、更安全。掌握这些核心对应关系,直接上手实战,Java 开发者能快速适配 Go 的开发思维。

https://go.dev/learn/
https://golang.google.cn/learn/
干起来再说!!!