一、方法定义的标准骨架
Go 的方法定义形式固定为:
func (接收者 接收者类型) 方法名(参数列表) (返回值列表) {
// 方法体
}
示例:
func (s *OrderService) BuildOrder(
ctx context.Context,
req *proto.OrderCreateReq,
) *proto.OrderCreateResp {
// …
}
拆解:
-
func:声明函数/方法的关键字
-
(s *OrderService):接收者,把方法挂到 OrderService 类型上,s 即方法内的“this”
-
BuildOrder:方法名
-
(ctx …, req …):参数列表,形式为 变量名 类型
-
*proto.OrderCreateResp:返回值,可以一个、多个或没有
对应 Java:
public class OrderService {
public OrderCreateResp buildOrder(Context ctx, OrderCreateReq req) { … }
}
本质差异:Java 方法写在 class { } 内部;Go 方法写在类型外部,通过接收者绑定类型。
二、方法 vs 函数:有没有接收者
| 写法 | func Name(…) | func (r T) Name(…) |
| 有无接收者 | 无 | 有 |
| 能否用“this” | 否 | 是 |
| 归属 | 包 | 类型 |
| Java 对应 | static 方法 | 实例方法 |
函数示例(纯数据转换,不依赖对象状态):
func ConvertOrderToDTO(order *model.Order) *dto.OrderDTO {
if order == nil {
return nil
}
return &dto.OrderDTO{
ID: order.ID,
Total: order.Total,
}
}
方法示例(需要访问对象字段或调用对象其他方法):
func (s *OrderService) ConvertOrdersToDTOs(orders []*model.Order) []*dto.OrderDTO {
result := make([]*dto.OrderDTO, 0, len(orders))
for _, o := range orders {
result = append(result, ConvertOrderToDTO(o)) // 复用上面的包级函数
}
return result
}
判断原则:
-
只依赖入参 → 写成函数(相当于 Java 的 static)
-
需要访问字段/其他方法 → 写成方法
另外,首字母大小写决定可见性:ConvertOrderToDTO 大写 → 跨包可调用(≈ public);convertOrders 小写 → 仅包内可见(≈ package-private)。
三、接收者变量名与包名同名
Go 允许变量名和包名同名,编译器按位置区分:
// 变量名 order 和包名 order 撞名,可读性差
func (order *OrderService) Handle(order *order.OrderModel) { … }
四、参数声明:名字在前,类型在后
Go 参数声明是 变量名 类型,和 Java 的 类型 变量名 方向相反:
| ctx context.Context | Context ctx |
| req *proto.OrderCreateReq | OrderCreateReq req |
| id int64 | long id |
| at time.Time | Instant at |
相同类型的多个参数可合并:
func Sum(a, b, c int) int { return a + b + c }
// 等价于 a int, b int, c int
五、包名.成员 的引用规则
import 的是“包”,不是“变量”。规则:
跨包访问成员(类型/函数/变量/常量)→ 必须带 包名.
本包内成员(含跨文件)→ 直接写名字,不带前缀
跨包访问未导出成员(首字母小写)→ 编译不过
import (
"context"
"time"
"example.com/project/model"
"example.com/project/proto"
)
func Example() {
var ctx context.Context // context 包
var t time.Time // time 包
var req *proto.OrderCreateReq // proto 包
var o model.Order // model 包,本包外
_ = ctx
_ = t
_ = req
_ = o
}
本包内定义的类型/函数直接用:
package order
type OrderService struct{}
func NewOrderService() *OrderService { return &OrderService{} } // 本包函数
func (s *OrderService) Ping() {} // 本包类型,不带包名
两个例外(工程中少用):
-
别名导入:import ordermodel "example.com/project/model" → 用 ordermodel.Order
-
dot import:import . "context" → 可写 Context,但破坏显式性,静态检查会告警,不推荐
六、指针、切片、map 的类型表达
| model.Order | 值类型(拷贝) | — |
| *model.Order | 指针(引用语义) | Order |
| []model.Order | 值切片 | List<Order>(但底层是数组) |
| []*model.Order | 指针切片(常用) | List<Order> |
| map[string]*model.Order | 键值 map | Map<String, Order> |
| map[int64][]*model.OrderItem | 复合 map | Map<Long, List<OrderItem>> |
几个要点:
struct 是值类型,按值传会拷贝整个结构体;加 * 避免拷贝,也允许 nil。
context.Context 是接口,接口天然引用语义,不加 *;*context.Context 几乎不写。
slice 和 map 内部本身含指针,但传参时拷贝的是“头信息”,底层数据共享;给 slice 重新赋值不影响原 slice,但修改元素会影响。
func (s *OrderService) Lookup(ids []int64) map[int64]*model.Order {
result := make(map[int64]*model.Order, len(ids)) // 必须 make 才能写
for _, id := range ids {
if o, ok := s.cache[id]; ok { // 缓存命中
result[id] = o
}
}
return result
}
七、map 存在性判断与 nil 防御
判断 key 是否存在,必须用 _, ok:
if v, ok := m[key]; ok {
// 存在,v 是真实值
}
不能只写 m[key] == nil——key 不存在和 key 存在但值为 nil 无法区分。
写 map 前必须 make,否则 panic: assignment to entry in nil map:
var m map[string]int // nil map
// m["a"] = 1 // panic!
m = make(map[string]int)
m["a"] = 1 // 正常
遍历 + 存在性检查 + 跳过的常见模式:
func (s *OrderService) CheckStock(
orders map[string][]*model.OrderItem,
stock map[string]int,
) []string {
missing := []string{} // 非 nil 空切片
for sku, items := range orders {
if _, ok := stock[sku]; !ok {
log.Warn("sku not in stock table, skip||sku=%s||count=%d", sku, len(items))
continue // 跳过本轮,处理下一个 sku
}
// 正常处理
}
return missing
}
nil 防御:Go 里解引用 nil 指针会 panic(虽然可 recover,但不推荐)。访问字段前判空:
func Total(order *model.Order) int64 {
if order == nil {
return 0
}
var sum int64
for _, item := range order.Items {
if item == nil {
continue
}
sum += item.Price * int64(item.Qty)
}
return sum
}
注意 []T{} 与 var s []T 的区别:
-
[]T{} → 非 nil 空切片,s != nil 为 true
-
var s []T → nil 切片,s == nil 为 true
-
两者 len 都是 0,但判 nil 结果不同
八、多返回值与错误处理
Go 没有受检异常,错误是普通返回值:
func (s *OrderService) Create(
ctx context.Context,
req *proto.OrderCreateReq,
) (*model.Order, error) {
if req == nil {
return nil, errors.New("nil request")
}
order, err := store.OrderStoreInstance.Insert(ctx, req)
if err != nil {
return nil, fmt.Errorf("insert order: %w", err)
}
return order, nil
}
调用方:
order, err := svc.Create(ctx, req)
if err != nil {
log.Warn("create failed: %v", err)
return
}
两种错误处理风格:
风格 A:(resp, err) 双返回(Go 惯用):
func (s *OrderService) Get(ctx context.Context, id int64) (*model.Order, error) {
if id <= 0 {
return nil, errors.New("invalid id")
}
return store.OrderStoreInstance.Find(ctx, id)
}
风格 B:错误码塞进响应体(RPC 框架常见,类似 controller):
func (s *OrderController) Create(
ctx context.Context,
req *proto.OrderCreateReq,
) *proto.OrderCreateResp {
resp := &proto.OrderCreateResp{Code: 0, Msg: "", Data: nil}
data, err := service.OrderServiceInstance.Create(ctx, req)
if err != nil {
resp.Code = -1
resp.Msg = err.Error()
resp.Data = data
return resp
}
resp.Data = data
return resp
}
对 Java 开发者:
Go 的 err != nil 替代 try/catch;
err.Error() ≈ e.getMessage();
fmt.Errorf("…: %w", err) ≈ 异常链 new RuntimeException("…", e)。
九、Java ↔ Go 速查表
| 方法归属 | 类内实例方法 | 接收者 (r *T) |
| “this” | 隐式 | 接收者变量名(常叫 r/this) |
| 静态方法 | static | 包级函数 |
| 引用/值 | 对象即引用 | T 值拷贝,*T 指针 |
| 空值 | null | nil |
| 字段访问 | obj.field / getField() | obj.field(同包直接点,无 getter) |
| 方法调用 | obj.method() | obj.method() |
| 长度 | list.size() | len(slice) |
| key 存在 | map.containsKey(k) | _, ok := m[k] |
| 错误 | throw / try-catch | (result, err) + err != nil |
| 错误消息 | e.getMessage() | err.Error() |
| 异常链 | new Ex("…", cause) | fmt.Errorf("…: %w", err) |
| 类型可见性 | public/private | 首字母大写/小写 |
| 跨包引用 | import 后短类名 | 必须 包名.成员 |
| 参数顺序 | 类型 名字 | 名字 类型 |
| 上下文 | ThreadLocal / gRPC Context | ctx context.Context 第一参数 |
| 多返回值 | 需包装类/Tuple | 原生 (A, B) |
十、练习:把下面的 Java 改写成 Go
Java:
public class UserService {
private Map<Long, User> cache = new HashMap<>();
public User findOrLoad(long id) {
if (cache.containsKey(id)) {
return cache.get(id);
}
User u = loadFromDb(id);
if (u != null) {
cache.put(id, u);
}
return u;
}
}
Go 参考:
package user
type UserService struct {
cache map[int64]*model.User // 小写 → 包内私有字段
}
func NewUserService() *UserService {
return &UserService{cache: make(map[int64]*model.User)}
}
func (s *UserService) FindOrLoad(ctx context.Context, id int64) (*model.User, error) {
if u, ok := s.cache[id]; ok { // _, ok 判断存在
return u, nil
}
u, err := loadFromDB(ctx, id)
if err != nil {
return nil, fmt.Errorf("load user %d: %w", id, err)
}
if u != nil {
s.cache[id] = u
}
return u, nil
}
