在Java里,final字段必须在对象构造阶段完成初始化,之后不可修改。如果希望每个实例都拥有一个全局递增且唯一的ID,就需要一套在构造期安全分配编号的机制。下面介绍几种常见且实用的实现策略。

使用AtomicLong静态计数器
最简单直接的办法是在类中声明一个静态的AtomicLong,每次构造对象时通过incrementAndGet方法获取下一个ID。由于AtomicLong本身是线程安全的,即使在多线程环境下创建对象,也能保证ID不重复且递增。
public class User {
private static final java.util.concurrent.atomic.AtomicLong ID_GENERATOR
= new java.util.concurrent.atomic.AtomicLong(0);
private final long id;
public User() {
this.id = ID_GENERATOR.incrementAndGet();
}
public long getId() {
return id;
}
}
优点与局限
- 实现简单,依赖JDK标准库
- ID从1开始严格递增,便于排查问题
- 静态状态属于类级别,在大量子类或复杂类加载场景中需留意生命周期
通过工厂方法封装构造
如果不想让调用方直接new对象,可以私有化构造器,并提供静态工厂方法。这样ID分配逻辑集中在一处,也方便后续替换为其他算法。
public class Order {
private static long sequence = 0;
private final long id;
private Order(long id) {
this.id = id;
}
public static synchronized Order create() {
sequence++;
return new Order(sequence);
}
public long getId() {
return id;
}
}
上面使用了synchronized保证多线程安全。如果追求更高性能,可改用具名锁或原子类。
借助UUID与序号结合
某些业务需要ID具备一定随机性,但仍要保持递增趋势。可以用AtomicLong生成序号,再拼装时间戳或机器标识,赋值给final属性。
public class Task {
private static final java.util.concurrent.atomic.AtomicLong SEQ
= new java.util.concurrent.atomic.AtomicLong(0);
private final String taskId;
public Task() {
long n = SEQ.incrementAndGet();
this.taskId = System.currentTimeMillis() + "-" + n;
}
public String getTaskId() {
return taskId;
}
}
策略对比
| 策略 | 线程安全 | ID形态 | 适用场景 |
|---|---|---|---|
| AtomicLong计数器 | 是 | 纯数字递增 | 内部实体编号 |
| 工厂方法+synchronized | 是 | 纯数字递增 | 需控制实例来源 |
| UUID与序号结合 | 是 | 时间戳加序号 | 分布式或日志追踪 |
小结
为final属性生成递增唯一ID的核心在于:把计数动作放在构造路径上,并利用线程安全工具避免竞争。根据业务对ID可读性和分布式能力的诉求,选择静态原子类、工厂方法或混合方案即可。