Java 封装
Java 封装
在各个面向对象语言里,面向对象有三大特征,即封装、继承和多态。下面我们将详细探讨 Java 中的封装,包括其概念、特点、为何需要封装、实现方式,最后通过一个 NBA 球员类的案例来加深理解。
1. 概念和特点
封装是将对象的状态(属性)和行为(操作数据的方法)捆绑在一起,并对外界隐藏其内部实现细节的过程。类的基本作用就是封装代码,它把类的一些特征和行为隐藏在类内部,不允许类外部直接访问,而是通过类提供的方法来实现对隐藏信息的操作和访问,相当于设置了一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
封装有两个显著特点:
- 只能通过规定的方法访问数据;
- 隐藏类的实例细节,方便修改和实现。
在日常生活中,封装的例子随处可见,例如智能手机。我们无需关心其内部复杂的逻辑电路设计,只需通过手机的屏幕、按键、充电口、耳机接口等外部接口就能对手机进行操作和使用。复杂的逻辑电路以及模块被封装在手机内部,而这些必要接口的留出,让我们更简便地使用手机,同时也保护了手机的内部细节。
2. 为什么需要封装
封装具有诸多优点:
- 提高内聚性:有利于提高类的内聚性,适当的封装能让代码更易于理解和维护。类的内部数据操作细节自己完成,不允许外部干涉。
- 降低耦合度:良好的封装可降低代码的耦合度,仅暴露少量的方法给外部使用,尽量方便外部调用。
- 增强安全性:一些关键属性只允许类内部访问和修改,通过访问修饰符限制对对象内部数据的直接访问,还能实施更精细的控制逻辑,如数据验证,从而确保数据的完整性和一致性,增强类的安全性。
- 简化接口:隐藏实现细节,为调用方提供易于理解的接口,用户只需知道对象能做什么(通过其提供的方法),而无需了解具体实现方式,符合黑盒设计原则。
- 方便修改:当需求发生变动时,只需修改封装的代码,而不用到处修改调用处的代码,提高了软件的可维护性和扩展性。
3. 实现封装
在 Java 语言中,实现封装需要以下 3 个步骤:
- 修改属性可见性:将属性的可见性修改为
private
,这表明该属性只能在当前类内被访问,出了这个类就无法直接访问。 - 创建公开的 getter 和 setter 方法:通常以
get + 属性名
的方式命名 getter 方法,用于返回对应的私有属性;以set + 属性名
的方式命名 setter 方法,用于给对应属性进行赋值。这样在类的外部就可以通过调用这些方法对属性进行操作。 - 进行属性合法性判断:在 getter 和 setter 方法中,对属性的合法性进行判断。例如,对于年龄属性,可判断其是否为正值。
下面通过一个 NBA 球员类 NBAPlayer
来展示封装的实现:
public class NBAPlayer {
// 姓名
private String name;
// 年龄
private int age;
// 无参构造方法
public NBAPlayer() {
}
// 单参构造方法
public NBAPlayer(int age) {
this.setAge(age);
}
// 全参构造方法
public NBAPlayer(String name, int age) {
this.setName(name);
this.setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 判断参数 age 的合法性
if (age < 0) {
this.age = 0;
} else {
this.age = age;
}
}
public static void main(String[] args) {
NBAPlayer james = new NBAPlayer();
// 对属性赋值:
james.setName("詹姆斯");
james.setAge(35);
// 打印 james 实例属性
System.out.println("姓名:" + james.getName());
System.out.println("年龄:" + james.getAge());
System.out.println("-------------");
// 实例化一个新的对象
NBAPlayer jordan = new NBAPlayer("乔丹", 60);
// 打印 jordan 对象实例属性
System.out.println("姓名:" + jordan.getName());
System.out.println("年龄:" + jordan.getAge());
}
}
在上述代码中,最初定义的 NBAPlayer
类没有进行封装,外部可以直接对属性进行赋值,可能会出现不合理的数据,如年龄为负数。通过封装,将属性 name
和 age
设为 private
,并提供了相应的 getter 和 setter 方法,同时在 setAge
方法中对年龄的合法性进行了判断,避免了不合理数据的输入。如果在类外部有很多地方都会操作属性值,当属性值读写逻辑发生改变时,只需修改类内部的逻辑即可。另外,在有参构造方法中,对属性赋值时直接调用其 setter 方法,无需再写重复的逻辑判断,提高了代码复用性。
4. 小结
封装是面向对象的重要特征之一,它隐藏了对象的信息,并且留出了访问的接口。通过封装,我们可以提高代码的安全性、可维护性和复用性,使代码更加模块化和易于使用。