Java面向对象进阶:构造器、封装与权限修饰符
前言
学完类、对象、属性、方法这些基础后,你已经能用Java描述现实中的事物了。但真正的面向对象编程,还差几块关键拼图。
本文将带你走进构造器、封装、权限修饰符、this关键字与JavaBean规范的世界。它们是写出"规范、安全、可维护"Java代码的基石。
一、构造器(Constructor)
1.1 什么是构造器
构造器是类的第三个成员(前两个是成员变量和成员方法),它是一种特殊的方法。
打个比方:类是生产"产品"的图纸,而构造器就是生产流水线上的"组装工人"。每次你想造一个对象,都得请这位工人来组装。
它的写法如下:
java
public class Student {
private String name; // 实例变量:姓名
private int score; // 实例变量:成绩
// 这就是构造器:名字和类名一样,没有返回值类型
public Student(String n, int s) {
name = n;
score = s;
}
}
1.2 构造器的特点
构造器有四个鲜明特征,和普通方法区别开:
- 名称必须与类名完全一致,大小写也要一样。
- 没有返回值类型,连 void 都不能写。一旦写了返回值类型,它就变成了普通方法,不再是构造器。
- 修饰符只能是 public、protected、缺省(不写)、private 这四种之一。
- 每个类都有构造器,绝不会出现"一个构造器都没有"的情况。
小贴士:构造器与普通方法最大的区别就是"无返回值类型"。新手常犯的错误是给构造器加上 void,结果它就不再是构造器了。
1.3 默认构造器
如果你一个构造器都没写,编译器会"贴心地"帮你加上一个无参构造器:
java
public class Student {
private String name;
// 你没写任何构造器,编译器自动补上:
// public Student() { }
}
但要注意一条铁律:只要你手动写了任何一个构造器(无论有参无参),编译器就不再帮你补默认无参构造了。
注意:这是非常常见的坑。很多框架(如Spring、MyBatis)创建对象时会调用无参构造,如果你只写了有参构造而忘了无参构造,程序会报错。建议:只要写了有参构造,就顺手补一个无参构造。
1.4 构造器的作用
构造器有两个核心使命:
- 与 new 一起创建对象------它是对象诞生的"接生婆"。
- 为实例变量初始化------在对象出生的瞬间就给它赋上合理的值。
java
public class StudentTest {
public static void main(String[] args) {
// new Student("张三", 89) 做了两件事:
// 1. 创建一个 Student 对象
// 2. 调用构造器,把 name 设为"张三",score 设为 89
Student s = new Student("张三", 89);
System.out.println(s.getInfo());
}
}
如果没有构造器初始化,实例变量只会是默认值(int 为 0,引用类型为 null),往往不是我们想要的。
1.5 构造器的重载
和方法一样,构造器也可以重载:名字相同,参数列表不同。这样就能用多种方式创建对象。
java
public class Student {
private String name;
private int score;
// 构造器1:只传姓名
public Student(String n) {
name = n;
}
// 构造器2:传姓名和成绩
public Student(String n, int s) {
name = n;
score = s;
}
// 构造器3:只传成绩
public Student(int s) {
score = s;
}
public String getInfo() {
return "姓名:" + name + ",成绩:" + score;
}
}
调用时根据传入参数的个数和类型,Java会自动匹配合适的构造器,就像点餐时选不同的套餐。
1.6 快捷键生成构造器
在IDEA中,按 Alt + Insert(部分笔记本需要 Fn + Alt + Insert),选择 Constructor,勾选要初始化的属性,即可一键生成构造器。
这能避免手写时漏掉字段或拼错名字,是日常开发的高效利器。
二、封装
2.1 封装的概念与意义
面向对象有三大特征:封装、继承、多态。封装是其中最基础的一个。
生活中的封装就像快递包裹:纸箱把商品包起来,既保护商品不被损坏,又隐藏了里面具体装了什么,还方便搬运。
Java中的封装,就是把对象的内部细节隐藏起来,只对外暴露必要的访问方式。它带来三大好处:
- 安全:保护内部成员变量,外部不能随意修改,只能通过规定的方法访问。
- 简便:外部使用者不需要关心内部如何实现,只要会调方法即可。
- 可维护:内部实现变了,只要对外接口不变,外部代码无需改动。
2.2 四种权限修饰符
Java通过权限修饰符来实现封装。共有四种,可见性范围由小到大:
| 修饰符 | 本类内部 | 本包的其他类 | 其他包的子类 | 其他包的非子类 |
|---|---|---|---|---|
| private | 可以 | 不可以 | 不可以 | 不可以 |
| 缺省(不写) | 可以 | 可以 | 不可以 | 不可以 |
| protected | 可以 | 可以 | 可以 | 不可以 |
| public | 可以 | 可以 | 可以 | 可以 |
记忆口诀:从 private 到 public,可见范围像洋葱一样一层层扩大。private 最严(只对自己开放),public 最宽(对全世界开放)。
小贴士:缺省(default)不是关键字,而是"什么都不写"时的默认状态。它允许同包访问,但不允许跨包。
2.3 修饰类与修饰成员的规则
权限修饰符既可以修饰类的成员(属性、方法、构造器),也可以修饰类本身,但规则略有不同:
- 修饰类:只能用 public 或缺省。不能用 private、protected 修饰外部类(内部类除外)。
- 修饰成员:四种修饰符都可以用。
换句话说,一个类对外的"可见性"只有两档:要么全包可见(缺省),要么全工程可见(public)。而类内部的成员,则有更细粒度的控制。
2.4 属性私有化与 getter/setter
封装最典型的应用就是属性私有化:把成员变量声明为 private,然后提供公开的 getter/setter 方法来访问它。
为什么这样做?假设有个年龄属性,如果直接 public 暴露,谁都能赋值为 -100 或 9999。但通过 setter,我们就能加校验逻辑:
java
public class Employee {
private String name; // 属性私有化,外部无法直接访问
private int age;
// getter:供外部读取属性值
// 命名规范:get + 属性名(首字母大写)
public int getAge() {
return age;
}
// setter:供外部修改属性值
// 命名规范:set + 属性名(首字母大写)
public void setAge(int age) {
// 在这里可以做合法性校验,保证数据安全
if (age < 0 || age > 150) {
System.out.println("年龄不合法,已设为默认值0");
this.age = 0;
} else {
this.age = age;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
getter/setter 的规范要点:
- getter 以
get开头,属性名首字母大写;boolean 类型用is代替get(如isMarry())。 - getter 有返回值、无参数;setter 无返回值(void)、有参数。
- 静态变量的 getter/setter 也都是静态的,实例变量的 getter/setter 是非静态的。
注意:boolean 类型属性的 getter 用 is 开头更符合英语习惯,比如
isMarry()、isEmpty()。
生成 getter/setter 同样用 Alt + Insert,选择 Getter / Setter / Getter and Setter 即可批量生成。
三、this关键字详解
3.1 this是什么
this 是一个代词,代表当前正在调用这个方法的对象。谁调用了这个方法,this 就指向谁。
它可以用在:构造器、实例方法、非静态代码块。
它不能出现在:静态方法、静态代码块。
原因很简单:静态方法属于类本身,不属于任何对象,自然没有"当前对象"一说。
3.2 this.成员变量------区分重名
当方法的局部变量(包括参数)与实例变量同名时,Java遵循"就近原则",会优先使用局部变量。这时如果想让编译器认出实例变量,就要加 this.:
java
public class Employee {
private String name;
private double salary;
// 参数 name 和实例变量 name 重名
// 不加 this. 的话,name = name 相当于自己赋值给自己,实例变量没变
public Employee(String name, double salary) {
this.name = name; // this.name 表示实例变量
this.salary = salary; // this.salary 表示实例变量
}
public void setName(String name) {
this.name = name; // this 代表调用 setName 的那个对象
}
}
注意:如果局部变量和实例变量不重名,可以省略 this.,编译器会自动识别为实例变量。但养成加 this. 的习惯,能让代码可读性更强。
3.3 this()------调用本类其他构造器
this 还有一种用法:this(实参列表),用于在一个构造器中调用本类的另一个构造器,实现代码复用。
java
public class Employee {
private String name;
private double salary;
private char gender;
// 无参构造
public Employee() {
System.out.println("一个员工对象被创建了");
}
// 部分参数构造:复用无参构造的代码
public Employee(String name, double salary) {
this(); // 调用本类的无参构造,复用其中的代码
this.name = name;
this.salary = salary;
}
// 全参构造:复用上面那个部分参数构造
public Employee(String name, double salary, char gender) {
this(name, salary); // 调用本类的另一个构造器
this.gender = gender;
}
}
注意:
this()调用必须放在构造器的第一行。一个构造器中只能调用一次其他构造器。这个位置限制是为了保证对象初始化的顺序清晰。
四、对象数组
4.1 什么是对象数组
数组不仅能存基本类型(int、double),也能存对象。所谓对象数组,就是元素类型为某个类的数组。
关键理解:数组中存的并不是对象本身,而是对象的引用(首地址)。就像一排信箱,每个信箱里装的不是房子,而是房子的门牌号。
java
// 声明一个矩形对象数组
Rectangle[] arr = new Rectangle[3];
此时数组有3个格子,但每个格子的默认值是 null------还没装任何对象进去。
4.2 对象数组的使用
下面是一个完整示例:创建3个矩形对象存入数组,遍历输出,再按面积排序后输出。
java
// 矩形类:属性私有、有构造器、getter/setter、业务方法
public class Rectangle {
private double length;
private double width;
public Rectangle() {
}
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
// 面积
public double area() {
return length * width;
}
// 周长
public double perimeter() {
return 2 * (length + width);
}
// 综合信息
public String getInfo() {
return "长:" + length + ",宽:" + width + ",面积:" + area() + ",周长:" + perimeter();
}
}
测试类:
java
public class RectangleTest {
public static void main(String[] args) {
// 动态初始化:创建长度为3的矩形数组,元素默认值是 null
Rectangle[] arr = new Rectangle[3];
// 给数组元素赋值:每个位置都要 new 一个对象
arr[0] = new Rectangle(5, 2);
arr[1] = new Rectangle(6, 3);
arr[2] = new Rectangle(5, 1);
// 遍历输出(普通for循环)
for (int i = 0; i < arr.length; i++) {
// arr[i] 是一个对象引用,可以调用它的属性和方法
System.out.println(arr[i].getInfo());
}
// 按面积从小到大排序(冒泡排序)
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
// 对象不能直接比较大小,要用某个属性或方法返回值
if (arr[j].area() > arr[j + 1].area()) {
// 交换的是数组中的引用(门牌号),不是对象本身
Rectangle temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 排序后遍历输出(增强for循环)
for (Rectangle r : arr) {
System.out.println(r.getInfo());
}
}
}
注意:对象数组中,数组元素本身是引用 ,交换元素交换的是引用(门牌号),对象本身在堆内存中并没有移动。对象之间不能直接用
>比较,要用具体属性(如area())来比较。
五、标准JavaBean规范
5.1 JavaBean是什么
JavaBean 是Java中一种标准的类编写规范。符合规范的类被称为 JavaBean,它们能被各种框架、工具识别和使用。
5.2 编写规范
一个标准的JavaBean要满足以下几点:
- 属性私有化:成员变量都用 private 修饰。
- 提供无参构造:方便框架反射创建对象。
- 提供有参构造(可选):方便快速初始化。
- 提供 getter/setter:供外部读写属性。
下面是一个完整的标准JavaBean示例:
java
public class Customer {
// 1. 属性私有化
private String name;
private char gender;
private int age;
private String tel;
private String email;
// 2. 无参构造
public Customer() {
}
// 3. 有参构造(全参)
public Customer(String name, char gender, int age, String tel, String email) {
this.name = name;
this.gender = gender;
this.age = age;
this.tel = tel;
this.email = email;
}
// 4. getter/setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// 通常还会提供一个 getInfo 方法,方便打印对象信息
public String getInfo() {
return "姓名:" + name + ",性别:" + gender + ",年龄:" + age
+ ",电话:" + tel + ",邮箱:" + email;
}
}
5.3 IDE快捷键大全
IDEA为JavaBean的编写提供了强大的快捷键支持,熟练使用能极大提升效率:
Alt + Insert:弹出"生成"菜单,包含以下所有选项。- Constructor:生成构造器(可选无参、有参)。
- Getter:生成getter方法。
- Setter:生成setter方法。
- Getter and Setter:同时生成getter和setter。
- toString():生成toString方法,方便打印对象。
- equals() and hashCode():生成equals和hashCode方法。
Ctrl + P:查看构造器/方法的参数列表(调用时按)。数组名.fori:快速生成普通for循环。iter:快速生成增强for循环。
小贴士:写完属性后,依次用 Alt+Insert 生成 Constructor(全选属性生成有参构造,再生成一个无参)、Getter and Setter、toString,一个标准JavaBean几秒就能搭好。
六、小结
本文围绕"写出规范的Java类"这一目标,串联了几个核心知识点:
-
构造器:与类同名、无返回值类型,负责创建对象和初始化实例变量。不写时编译器补默认无参构造,写了就不再补。可重载,可用 Alt+Insert 快速生成。
-
封装:隐藏内部细节,对外暴露必要接口。通过权限修饰符实现,带来安全、简便、可维护三大好处。
-
权限修饰符:private(只对本类)、缺省(本包)、protected(本包+其他包子类)、public(全部)。类只能用 public 或缺省,成员四种皆可。
-
属性私有化:属性用 private,配 getter/setter 访问,可在 setter 中加校验。boolean 的 getter 用 is 开头。
-
this关键字:代表当前对象。用于区分重名变量(this.成员变量)、调用本类构造器(this() 必须在首行)。静态方法中不能用 this。
-
对象数组:存储对象引用的数组。元素默认 null,需 new 对象赋值。通过 arri.方法() 调用。比较对象要用具体属性。
-
JavaBean规范:属性私有 + 无参构造 + 有参构造 + getter/setter。配合 Alt+Insert 快捷键,几秒钟搭好一个标准类。
掌握这些,你就具备了写出"工业级"Java类的能力。下一篇我们将进入继承与多态,看看如何让类之间产生血缘关系,复用更多代码。