Java中声明对象引用与调用构造器实例化的区别及优缺点
核心差异
两种写法的本质区别是有没有完成「引用变量和实际对象实例的绑定」,内存层面的行为完全不同:
- 写法1:
Date date;
仅在当前作用域的栈空间创建了一个Date类型的引用变量,没有在堆内存中创建任何实际的Date对象。这个引用本质是个“空壳”:如果是类的成员变量,虚拟机会给它默认赋值null;如果是方法内的局部变量,在你手动给它赋值之前,任何访问它的操作都会直接触发编译错误,代码根本跑不起来。 - 写法2:
Date date = new Date();
这行代码实际执行了三个步骤:- 和写法1一致,在栈空间声明一个
Date类型的引用变量date - 通过
new Date()在堆内存开辟对应大小的空间,调用Date的无参构造器完成实例初始化,生成一个真实存在的Date对象 - 把新创建的Date对象的内存地址赋值给
date引用,让这个引用指向真实对象,这时候可以直接调用date.getTime()之类的实例方法,不会报错。
- 和写法1一致,在栈空间声明一个
优缺点与适用场景
仅声明引用(Date date;)
- 优点
- 不会提前创建对象占用堆内存,避免无意义的资源开销——如果后续逻辑走到某个分支根本用不上这个变量,提前new对象属于纯粹的性能浪费
- 绑定对象的灵活性高,后续可以给它赋值任意Date类型(包括其子类)的实例,不需要在声明阶段就固定对象的创建逻辑
- 缺点
- 使用前必须手动赋值/做非空校验:成员变量场景下直接用会抛空指针,局部变量场景下没赋值直接编译失败
- 如果逻辑分支复杂,很容易出现某个分支漏赋值的问题,这类bug藏在分支里排查成本不低
- 适用场景
- 多分支赋值场景:比如需要根据入参类型、业务操作结果给
date赋不同的时间值,提前声明引用,在if/else、switch等分支里分别实例化赋值即可 - 延迟初始化场景:如果Date对象的创建成本很高(比如需要读取配置、计算复杂初始值),可以先声明引用,等第一次真正用到这个对象的时候再实例化,降低程序启动的瞬时开销
- 旧编码规范要求的「作用域顶部统一声明变量」场景(目前主流Java编码规范更推荐变量就近声明,这类用法已经很少)
- 多分支赋值场景:比如需要根据入参类型、业务操作结果给
声明同时实例化(Date date = new Date();)
- 优点
- 声明完成即可直接使用,不需要额外考虑未赋值、空指针的问题,代码更不容易出低级错误
- 可读性强,其他开发者看代码一眼就能知道这个变量的初始值是什么,逻辑直白,不需要追着找赋值位置
- 可以配合
final关键字修饰,实现引用不可变,天然规避后续逻辑误把引用改指向null、其他对象的问题,适合多线程场景下的共享变量
- 缺点
- 只要代码执行到这一行,就会立刻创建对象占用堆内存,如果后续逻辑根本没用到这个变量,或者对象创建成本很高,就会带来不必要的性能、内存浪费
- 灵活性差,声明阶段就固定了对象的实例化逻辑,后续如果要根据场景替换不同的子类实例、不同初始值的对象,需要修改初始化代码
- 适用场景
- 变量声明后立刻就要使用、且初始值固定的场景:比如需要获取当前时间做时间戳计算,直接new完就用,不需要额外分支判断
- 逻辑简单的局部临时变量场景:方法内的固定逻辑计算、临时值存储,直接初始化能避免漏赋值的低级bug
- 不可变常量/线程安全共享变量场景:配合
final修饰,声明即初始化,后续不允许修改引用指向,减少并发问题
常见误区提醒:别把仅声明的引用当成“创建了一个空对象”。
null不是空对象,它的含义是「这个引用不指向任何有效对象」,只要对着值为null的引用调用实例方法,100%会抛出NullPointerException。
内容的提问来源于stack exchange,提问作者Amos Machora
相关产品推荐
相关产品推荐

