Java 只有引用,Go 的指针 * 到底是什么?内存模型一次讲透
📅 2026/7/19 4:08:44
👁️ 阅读次数
📝 编程学习
本文为《Java工程师转Go实战》连载第 4 篇 / 共 20 篇
上一篇:基础语法一天速通
下一篇:没有 class 和继承,Go 怎么写业务?
类比开场
Java 里你很少写指针,但每天都在用:对象变量本质是引用的拷贝。
Usera=newUser("张三");Userb=a;b.setName("李四");// a.getName() 也变成 "李四"Go 把这件事说得更直白:你可以选择传值还是传指针,而且语法里直接有*和&。
一、三个符号搞懂 80%
| 符号 | 读法 | 含义 |
|---|---|---|
&x | 取地址 | 得到 x 的指针 |
*p | 解引用 | 通过指针访问指向的值 |
*T | 类型 | 「指向 T 的指针」类型 |
x:=42p:=&x// p 的类型是 *intfmt.Println(*p)// 42*p=100fmt.Println(x)// 100二、Java 引用 vs Go 指针
| Java 对象 | Go 值类型 | Go 指针 | |
|---|---|---|---|
| 赋值 | 引用拷贝 | 整份拷贝 | 地址拷贝 |
| 修改影响原对象 | 是 | 否 | 是 |
| 空 | null | 零值 | nil |
| 谁管内存 | GC | 栈或堆,编译器决定 | 同左 |
typeCounterstruct{Nint}funcaddValue(c Counter){c.N++}// 不改外面funcaddPtr(c*Counter){c.N++}// 改外面funcmain(){c:=Counter{N:0}addValue(c)fmt.Println(c.N)// 0addPtr(&c)fmt.Println(c.N)// 1}三、什么时候用指针?
| 场景 | 建议 |
|---|---|
| 方法要修改 receiver 字段 | 指针接收者func (u *User) |
| 结构体很大 | 指针避免拷贝 |
| 需要表示「不存在」 | *User可为 nil |
| 小结构体、只读 | 值接收者即可 |
Java 对照:指针接收者 ≈ 在方法里改this的字段;值接收者 ≈ 改的是一个副本。
四、new 和 make(Java 人必混淆)
// new:分配内存,返回指针,置零值p:=new(int)// *int,值为 0// make:只用于 slice、map、channel,返回已初始化的引用类型s:=make([]int,0,10)m:=make(map[string]int)ch:=make(chanint)Java 里new ArrayList<>()一个概念搞定;Go 把slice/map/chan 的内部结构初始化单独拆给make。
五、逃逸分析:栈还是堆?
Java 程序员关心 JVM 堆栈;Go 程序员也要关心,但多数时候编译器帮你决定。
funcfoo()*int{x:=42return&x// x 逃逸到堆上——否则函数返回后地址失效}查看逃逸:
go build-gcflags="-m"./...面试常考:不是说有&就一定在堆上,逃逸分析才是依据。
六、nil 指针陷阱
varu*User=nil// u.Name // panic: nil pointer dereferenceifu!=nil{fmt.Println(u.Name)}和 JavaNullPointerException一样,先判空再解引用。
📦 面试追问
Go 有引用类型吗?
slice、map、channel 是引用类型(内部含指针),赋值后共享底层数据;但和 C++ 引用不同。值传递还是引用传递?
Go 永远是值传递。传指针时,拷贝的是指针值(地址),所以能改指向的对象。为什么 slice 改元素会影响原 slice?
slice 头是 {指针, len, cap},赋值拷贝的是 header,底层数组共享。
💡 一句话总结
Java 隐式给你引用,Go 显式让你选:值拷贝还是指针——搞懂&、*和接收者,就搞懂了 Go 一半的「对象」行为。
建议标签:Golang指针Java内存模型面试
编程学习
技术分享
实战经验