本节摘要:构造器是对象落地时的入籍手续——方法名与类同名、无返回类型,在 new 的瞬间被调用,负责把字段填到合法初值。本节拆解构造器的三条登记规则:默认构造器何时存在、重载如何互相调用(this 链)、以及 this 作为"我本人"引用的两种用法,并复现"写了带参构造器之后无参构造消失"的高频编译事故。
上一节我们看到:字段有默认值,int 是零、引用是空。但一份"姓名为空、面积为零"的档案上架,几乎等于废档。Java 的解法是在 new 的流程里强制插一道入籍仪式:分配内存、字段清零之后、返回引用之前,调用一次构造器。构造器的写法有三条辨识特征:名字与类完全相同、不写返回类型(连 void 都不写)、不能被对象显式调用——你没法在别的代码里写 r.Resident() 去再办一次入籍。
class Account { String owner; double balance; // 无参构造器:给保底初值 Account() { this.owner = "未登记"; this.balance = 0; System.out.println("入籍完成:一份数据齐整的档案已上架"); } } public class NewDemo { public static void main(String[] args) { Account a = new Account(); // new 的瞬间:分配内存 → 字段清零 → 调构造器 → 返回引用 System.out.println(a.owner); // 输出:未登记 System.out.println(a.balance); // 输出:0.0 } }
这里出现了 this 的第一种用法:this.字段名,指"当前这份档案的字段"。构造器参数也叫 owner 时,裸写 owner 会被当成参数(就近原则),this.owner 才是字段——这不是炫技,是构造器里避免重名歧义的标准写法。
一个高频翻车点值得单独立案。你不写任何构造器时,编译器白送一个什么都不做的无参构造器,所以 new Account() 能过。可一旦你写了任何一个带参构造器,白送的立刻收回:
class Account2 { String owner; double balance; // 只写了带参构造器 —— 默认无参构造器随即消失 Account2(String owner, double balance) { this.owner = owner; this.balance = balance; } } public class VanishDemo { public static void main(String[] args) { Account2 a = new Account2("赵六", 500.0); // 正常:走带参构造器 Account2 b = new Account2(); // 编译报错:找不到无参构造器 } }
为什么这是个事故:更麻烦的版本发生在继承场景(第 2 章)——子类构造器第一行会隐式调用父类的无参构造器,父类恰好只写了带参的,子类全线编译报错。工程习惯因此成型:只要写了带参构造器,就顺手补一个无参的;或者干脆用本节后面的 this 链把初值统一到一处。框架场景(反射建对象、序列化还原)也都默认调用无参构造器,留一个无参构造器是低成本高回报的保险。
入籍材料可能只来一部分:开户时只知道姓名,或者姓名与余额都知道。为不同材料量设不同构造器,就是构造器重载——同名不同参。规则与普通方法重载一致:参数个数、类型、顺序至少一项不同,与返回值无关(构造器本来就没有返回类型)。几个构造器之间常常存在"全量版"与"省略版"的关系,省略版把缺省值补上再委托全量版,用的就是 this 的第二种用法:this(参数),在构造器第一行调用同类的另一个构造器。
class Account3 { String owner; double balance; String branch; // 开户网点 // 全量版:唯一的"正式登记口径" Account3(String owner, double balance, String branch) { this.owner = owner; this.balance = balance; this.branch = branch; } // 省略版一:只知道姓名 —— 余额给零、网点给默认值,委托全量版 Account3(String owner) { this(owner, 0, "总所"); // this 链:必须是第一条语句 System.out.println("补办完成:缺省项已按默认值登记"); } // 省略版二:姓名加余额 —— 网点给默认值 Account3(String owner, double balance) { this(owner, balance, "总所"); } } public class ChainDemo { public static void main(String[] args) { Account3 a = new Account3("钱七"); Account3 b = new Account3("孙八", 800.0); System.out.println(a.branch + " / " + b.branch); // 输出:总所 / 总所 System.out.println(a.balance + " / " + b.balance); // 输出:0.0 / 800.0 } }
this 链有两条硬规矩:只能在构造器里用、必须是构造器的第一条语句。原因不复杂——入籍仪式只能走一遍,若允许先执行一段赋值再委托,被委托方又会重复或错序执行,登记口径就乱了。把默认值集中在一个"全量版"里,还有个隐性收益:口径改一处,全局生效。
完整案例:背景:会员系统初始化时有四种来源(姓名、姓名加余额、姓名加网点、全量),最初四个构造器各自为政,默认网点改一次要改三处,漏改一处数据就不一致。操作:改成上面的 this 链结构,默认值只写一遍。结果:新增"姓名加开户日期"来源时,只需加一个单行委托的构造器。解读:this 链的本质是把"默认值"从散落状态收拢成单一事实来源。变式:如果默认值需要运行时读取(比如从配置取网点名),静态代码块或工厂方法更合适——工厂方法在第 3 章接口与第 8 章反射处都会再遇到。
this 是"当前对象"的引用,但它的活动范围有明确边界。能做的:this.字段消歧、this(参数)构造器链、把 this 当参数传出去(事件注册的经典手法,注册的就是"我本人"这份档案)。不能做的:用在静态方法里——静态方法属于类不属于对象,调用它时可能根本没有任何对象存在,"我本人"无从谈起,编译器直接报错;以及 this(参数) 写在普通方法里或在构造器里出现两次。理解边界的钥匙还是那句话:先有对象,才有 this。
顺带补一个容易混的点:构造器是给 new 调用的"入籍手续",普通方法是对象落地后的"业务办理",两者都是类的成员,但构造器不参与继承、不能被 static 或 final 之外的大多数修饰符修饰。第 2 章讲继承时,super 调用父类构造器的规则几乎就是本节 this 链的镜像,两边对照着记事半功倍。
字段有了、入籍手续也有了,下一节解决权限问题:档案哪些栏目允许外人看、允许谁改——四种访问修饰符就是四级查看权限。