构造器与 this | JavaSE
构造器与 this
一、学习目标
学完本章,你应该能够:
- 能够解释什么是构造器(Constructor),以及构造器与普通成员方法的区别。
- 能够手写无参数构造器、有参数构造器和多个重载构造器。
- 能够解释创建对象时构造器为什么会被自动调用。
- 能够判断一个类是否拥有编译器隐式提供的默认构造器。
- 能够解释
this表示什么,并正确使用this.成员变量解决名称冲突。 - 能够根据对象初始化需求选择合适的构造器。
二、核心知识
2.1 为什么需要构造器
上一章已经学习过:
Student student = new Student();
student.name = "张三";
student.age = 18;
student.score = 95.5;
这种方式当然可以创建并初始化对象。
但是存在一个问题:
创建对象和给对象初始化数据被拆成了多个步骤。
如果一个 Student 对象创建出来以后,本来就应该立即拥有姓名、年龄、成绩,那么更自然的方式是:
Student student = new Student("张三", 18, 95.5);
这里:
new Student("张三", 18, 95.5)
调用的就是 Student 类的构造器。
构造器的主要作用之一,就是:
在创建对象时完成对象的初始化。
2.2 什么是构造器
构造器(Constructor)是类中的一种特殊成员。
基本语法:
修饰符 类名(参数列表) {
初始化代码;
}
例如:
public class Student {
String name;
int age;
public Student() {
System.out.println("Student对象正在创建");
}
}
其中:
public Student()
就是构造器。
2.3 构造器的基本特征
构造器有几个必须掌握的规则。
规则一:构造器名称必须与类名完全一致
类名:
Student
那么构造器必须写成:
public Student() {
}
不能写成:
public student() {
}
Java 区分大小写。
规则二:构造器不能声明返回值类型
正确:
public Student() {
}
错误:
public void Student() {
}
很多初学者看到:
public void Student()
以为它是构造器。
实际上不是。
因为一旦写了:
void
它就已经变成了一个普通成员方法,只不过这个方法恰好叫做 Student。
所以:
public Student() {
}
和:
public void Student() {
}
是完全不同的两个东西。
2.4 构造器什么时候执行
观察:
public class Student {
public Student() {
System.out.println("构造器执行了");
}
}
创建对象:
Student s1 = new Student();
Student s2 = new Student();
每执行一次:
new Student()
就会创建一个新的 Student 对象,并调用对应的构造器。
可以把过程暂时理解成:
执行 new Student()
↓
创建 Student 对象
↓
调用 Student()
↓
执行构造器中的初始化代码
↓
得到对象引用
↓
赋值给变量
因此:
Student s = new Student();
不能只理解成“new 一个对象”。
它实际上还包含:
寻找并调用与参数匹配的构造器。
2.5 无参数构造器
没有参数的构造器称为无参数构造器。
public class Student {
public Student() {
System.out.println("无参数构造器");
}
}
调用:
Student student = new Student();
2.6 有参数构造器
构造器也可以接收参数:
public class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
创建对象:
Student student = new Student("张三", 18);
这样对象在创建完成时,就已经拥有了初始化数据。
2.7 默认构造器
这是构造器最容易出错的规则之一。
假设:
public class Student {
}
我们虽然没有手写:
public Student() {
}
但下面代码仍然可以使用:
Student student = new Student();
原因是:
当一个普通类没有显式声明任何构造器时,Java 编译器会隐式声明一个默认构造器(Default Constructor)。
初学阶段可以近似理解为编译器帮我们补了:
public Student() {
}
但需要注意,默认构造器的访问权限并不永远都是 public,它会与类本身的可访问性相关,因此不要把“默认构造器一定是 public”当作规则记忆。
2.8 手写构造器以后会发生什么
看下面的类:
public class Student {
String name;
public Student(String name) {
this.name = name;
}
}
此时:
Student s1 = new Student("张三");
合法。
但是:
Student s2 = new Student();
会产生编译错误。
为什么?
因为:
只要类中显式声明了任意构造器,编译器就不会再隐式声明默认构造器。
所以如果既需要:
new Student()
又需要:
new Student("张三")
应该自己同时定义两个构造器:
public class Student {
String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
}
2.9 构造器重载
同一个类可以定义多个构造器。
例如:
public class Student {
String name;
int age;
double score;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
}
它们名称全部都是:
Student
但是参数列表不同。
这就是构造器重载(Constructor Overloading)。
因此可以:
Student s1 = new Student();
Student s2 = new Student("张三");
Student s3 = new Student("李四", 20);
Student s4 = new Student("王五", 21, 96.5);
不同的创建需求,可以选择不同的构造器。
2.10 什么是 this
this 是 Java 中非常重要的关键字。
在实例对象上下文中,可以先这样理解:
this 表示当前对象的引用。
例如:
public class Student {
public void printThis() {
System.out.println(this);
}
}
测试:
Student s1 = new Student();
Student s2 = new Student();
s1.printThis();
s2.printThis();
当:
s1.printThis();
执行时,方法内部的:
this
指向 s1 对象。
当:
s2.printThis();
执行时,方法内部的:
this
指向 s2 对象。
因此可以建立一个非常重要的理解:
谁调用实例方法
↓
this 就代表谁
构造器中的 this 则表示:
当前正在被构造的那个对象。
2.11 this 解决成员变量和局部变量同名问题
假设:
public class Student {
String name;
public Student(String name) {
name = name;
}
}
这里出现了两个 name。
构造器参数:
String name
属于局部变量。
成员变量:
String name;
属于对象。
当二者同名时:
name
默认优先找到距离当前位置更近的局部变量。
因此:
name = name;
实际上可以理解成:
局部变量 name = 局部变量 name
成员变量根本没有得到赋值。
正确写法:
public Student(String name) {
this.name = name;
}
其中:
this.name
表示:
当前对象的成员变量 name
右边:
name
表示:
构造器参数 name
所以:
this.name = name;
可以读成:
把参数
name的值赋给当前对象的成员变量name。
2.12 this 不只可以访问成员变量
this 本质上表示当前对象,因此也可以调用当前对象的实例方法:
public class Student {
String name;
public void study() {
System.out.println(name + "正在学习");
}
public void startLearning() {
this.study();
}
}
很多情况下:
this.study();
也可以简写为:
study();
因为当前实例上下文已经明确。
因此实际开发中最常看到的仍然是:
this.name = name;
这种用于消除成员变量与局部变量歧义的写法。
三、使用方法
3.1 完整 Student 示例
public class Student {
String name;
int age;
double score;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public void printInfo() {
System.out.println(
"姓名:" + name +
",年龄:" + age +
",成绩:" + score
);
}
}
测试:
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("张三");
Student s3 = new Student(
"李四",
20,
95.5
);
s1.printInfo();
s2.printInfo();
s3.printInfo();
}
}
这里体现了一个非常重要的设计思想:
让调用者根据自己拥有的数据选择合适的对象初始化方式。
3.2 如何判断应该设计几个构造器
没有固定答案。
例如一个用户对象:
User
如果业务允许:
new User()
那么可以提供无参构造器。
如果创建用户时姓名必须存在:
new User("LingXi")
那么可以提供:
public User(String username)
如果还需要一次性初始化更多信息:
new User("LingXi", 20, "lingxi@example.com")
还可以继续增加重载构造器。
核心原则不是:
构造器越多越好。
而是:
构造器应该表达对象合理的初始化方式。
四、原理与进阶
4.1 构造器不是普通方法
构造器与方法在语法上非常相似,但不能把构造器简单理解为“一个特殊名字的方法”。
例如构造器:
public Student() {
}
没有返回值类型。
普通方法:
public void printInfo() {
}
具有返回值类型声明:
void
在 Java 语言层面,二者属于不同的声明结构。
4.2 默认构造器不是永远存在
一定要区分两个概念:
情况一:没有声明任何构造器
public class Student {
}
编译器隐式声明默认构造器。
情况二:已经声明了一个构造器
public class Student {
public Student(String name) {
}
}
这时编译器不会再提供默认构造器。
所以:
new Student();
不能使用。
这是大量 Java 初学者第一次写实体类时最容易出现的问题之一。
4.3 this 为什么不能直接出现在 static 方法中
考虑:
public static void test() {
System.out.println(this);
}
这是错误的。
原因在于:
this
表示当前对象。
但静态方法属于类,不依赖某个具体实例对象才能调用:
Student.test();
调用时甚至可能根本不存在 Student 对象。
既然没有“当前对象”,自然也没有:
this
后面的 static 专章会系统讨论这一规则。
4.4 本章暂不展开 this(...)
Java 还允许在构造器之间通过:
this(...)
进行调用。
但这涉及构造器链以及后续的:
super(...)
为了保持章节边界,本章只掌握:
this
作为“当前对象引用”的含义。
this(...) 与 super(...) 将在后续 02-09 子类构造器与 this(...) / super(...) 中系统学习。
五、实践应用
5.1 用户对象初始化
public class User {
String username;
String email;
public User(String username, String email) {
this.username = username;
this.email = email;
}
}
使用:
User user = new User(
"LingXi",
"lingxi@example.com"
);
相比:
User user = new User();
user.username = "LingXi";
user.email = "lingxi@example.com";
第一种方式能够更加明确地表达:
创建用户对象时,需要同时提供这些初始化信息。
5.2 构造器不是数据校验的唯一位置
构造器可以对输入数据进行检查:
public Student(String name, int age) {
if (age >= 0 && age <= 150) {
this.age = age;
}
this.name = name;
}
不过,当对象的数据以后还可能被修改时,仅仅在构造器中校验并不够。
例如:
student.age = -100;
仍然可能破坏数据。
如何阻止外部代码随意修改成员变量,就是下一章:
封装(Encapsulation)
要解决的问题。
六、常见问题
6.1 构造器必须写吗?
不一定。
如果一个普通类没有声明任何构造器,编译器会隐式声明默认构造器。
但是实际开发中,如果对对象初始化方式有要求,通常会显式设计构造器。
6.2 构造器可以写 void 吗?
不能。
例如:
public void Student() {
}
这是普通方法,不是构造器。
6.3 一个类可以有多个构造器吗?
可以。
只要参数列表不同,就可以形成构造器重载。
6.4 写了有参构造器以后为什么 new Student() 报错?
因为一旦显式声明任意构造器,编译器就不再隐式提供默认构造器。
如果仍然希望使用:
new Student()
就需要自己声明:
public Student() {
}
6.5 this 是对象吗?
更准确地说:
this是一个引用当前对象的表达式。
因此:
this.name
表示当前对象中的 name 成员变量。
6.6 this.name = name 两个 name 是什么?
this.name = name;
左边:
this.name
是当前对象的成员变量。
右边:
name
是当前作用域中的局部变量或参数。
6.7 static 方法中为什么不能直接使用 this?
因为 this 必须对应某个具体对象,而静态方法属于类,可以在没有对象的情况下调用。
七、练习与验收
7.1 知识问答
- 什么是构造器?它主要解决什么问题?
- 构造器与普通成员方法在语法上有什么区别?
- 为什么构造器名称必须与类名一致?
- 构造器是否需要声明返回值类型?
- 创建对象时,构造器在什么时候执行?
- 一个类完全没有声明构造器时会发生什么?
- 什么是默认构造器?
- 显式声明一个有参构造器以后,默认构造器还存在吗?
- 为什么很多实体类会主动提供无参数构造器?
- 什么是构造器重载?
this在实例方法中表示什么?this在构造器中表示什么?this.name = name;两个name分别表示什么?this为什么可以解决成员变量与局部变量同名问题?- 静态方法中为什么不能直接使用
this?
7.2 代码阅读
阅读代码:
class Student {
String name;
public Student() {
System.out.println("A");
}
public Student(String name) {
this.name = name;
System.out.println("B");
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("张三");
System.out.println(s2.name);
}
}
不要运行代码,回答:
- 一共创建了几个
Student对象? - 创建
s1时调用哪个构造器? - 创建
s2时调用哪个构造器? - 三条输出语句最终按照什么顺序出现?
- 如果删除无参数构造器,
new Student()是否还能使用?为什么?
阅读代码:
class Student {
String name;
public void printThis() {
System.out.println(this);
}
}
public class ThisRead {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
s1.printThis();
s2.printThis();
System.out.println(s1);
System.out.println(s2);
}
}
回答:
- 哪两次输出表示同一个对象引用?
s1.printThis()中的this表示谁?s2.printThis()中的this表示谁?- 为什么同一个方法中的
this会因为调用对象不同而不同?
7.3 手写代码
任务一:Student 构造器
从零设计:
Student
包含:
name
age
score
要求:
- 提供无参数构造器。
- 提供只接收
name的构造器。 - 提供
name + age构造器。 - 提供
name + age + score构造器。 - 使用
this解决成员变量和参数同名问题。 - 创建至少 4 个对象验证不同构造器。
任务二:User 类
设计:
User
包含:
username
password
email
要求至少设计两种合理的对象初始化方式,并说明为什么需要这两个构造器。
7.4 Debug
下面代码存在问题:
public class Student {
String name;
int age;
public void Student(String name, int age) {
name = name;
age = age;
}
}
要求:
- 找出构造器声明中的错误。
- 判断
name = name的问题。 - 判断
age = age的问题。 - 修复代码。
- 使用下面代码验证:
Student s = new Student("张三", 18);
再分析:
public class User {
String username;
public User(String username) {
this.username = username;
}
}
测试:
User u1 = new User();
回答:
- 为什么编译失败?
- Java 是否仍然自动生成了无参构造器?
- 应该如何修改
User类才能同时支持两种创建方式?
7.5 综合训练
设计一个 Book 类。
包含:
title
author
price
要求:
- 设计至少三个构造器。
- 每个构造器代表一种合理初始化场景。
- 参数名称必须与成员变量名称一致。
- 使用
this完成赋值。 - 编写
printInfo()输出图书信息。 - 创建至少三个图书对象验证。
完成后说明:
为什么你选择这三个构造器,而不是随意增加大量构造器?
7.6 本章验收
不查看资料,检查自己能否完成:
- [ ] 30 秒内口述什么是构造器。
- [ ] 从零手写无参构造器。
- [ ] 从零手写有参构造器。
- [ ] 写出三个构造器形成重载。
- [ ] 解释默认构造器什么时候存在。
- [ ] 解释为什么写了有参构造器以后
new Student()可能报错。 - [ ] 解释
this表示什么。 - [ ] 解释
this.name = name。 - [ ] 判断为什么 static 方法不能直接使用
this。 - [ ] 不依赖 IDE 自动生成,从零完成一个带多个构造器的实体对象。
如果以上内容仍有任何一项需要依赖提示才能完成,本章就还没有达到真正掌握的标准。