欢迎光临
我们一直在努力

Go 语言学习笔记(Java 视角):方法、字段、包与错误处理

 一、方法定义的标准骨架

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 的 类型 变量名 方向相反:

GoJava
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 的类型表达

    Go 类型含义Java 近似
    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 速查表

    概念JavaGo
    方法归属 类内实例方法 接收者 (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
    }

    赞(0)
    未经允许不得转载:171主机测评 » Go 语言学习笔记(Java 视角):方法、字段、包与错误处理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址