说明
在前面的程序中经常出现通过某个对象直接访问其Field(成员变量)的情形,这可能引起一些潜在的问题。比如我们给某个Person类中的age成员变量赋值为1000,这在语法上来说是合法的,但是违背了现实。因此,Java程序推荐将类和对象的Field进行封装。
理解封装
封装是面向对象三大特征之一(其他两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
对一个类或对象实现良好的封装,可以实现以下目的:
- 隐藏类的实现细节。
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入逻辑控制,限制对Field的不合理访问。
- 可进行数据检查,从而有利于保证对象信息的完整性。
- 便于修改,提高代码可维护性。
为了实现良好的封装,需要从两个方面考虑:
- 将对象的Field和实现细节隐藏起来,不允许外部直接访问
- 把方法暴露出来,让方法来控制对这些Field来进行安全的访问和操作
这两个方面都需要访问控制符来实现。
访问控制符
说明
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java支持4种不同的访问权限。
- 默认的,也称为 default,在同一包内可见,不使用任何修饰符。
- 私有的,以 private 修饰符指定,在同一类内可见。
- 共有的,以 public 修饰符指定,对所有类可见。
- 受保护的,以 protected 修饰符指定,对同一包内的类和所有子类可见。
默认访问修饰符-不使用任何关键字
如果你声明一个字段,方法,构造函数,但你不写明访问修饰符,这意味着这将使用默认(default)的访问修饰符。
默认(default)的访问修饰符的访问范围是在包里面,即在同一个包,你可以访问成员的访问修饰符是默认(default),而且,不能访问包之外的东西,即使它们是一个子类。
如下例所示,变量和方法的声明可以不使用任何修饰符。
String version = "1.5.1";
boolean processOrder() {
return true;
}
私有访问修饰符-private
私有访问修饰符是最严格的访问级别,所以被声明为private的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为private。
声明为私有访问类型的变量只能通过类中公共的getter方法被外部类访问。
Private访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
下面的类使用了私有访问修饰符:
public class Logger {
private String format;
public String getFormat() {
return this.format;
}
public void setFormat(String format) {
this.format = format;
}
}
公有访问修饰符-public
被声明为public的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的public类分布在不同的包中,则需要导入相应public类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。
以下函数使用了公有访问控制:
public static void main(String[] args) {
// ...
}
Java程序的main() 方法必须设置成公有的,否则,Java解释器将不能运行该类。
受保护的访问修饰符-protected
被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问,也能够被不同包中的子类访问。
Protected访问修饰符不能修饰类和接口,方法和成员变量能够声明为protected,但是接口的成员变量和成员方法不能声明为protected。
子类能访问Protected修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
下面的父类使用了protected访问修饰符,子类重载了父类的openSpeaker()方法。
class AudioPlayer {
protected boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
class StreamingAudioPlayer {
boolean openSpeaker(Speaker sp) {
// 实现细节
}
}
访问控制符的几条基本原则
类里的绝大部分Field都应当使用private修饰,只有一些static修饰的、类似全局变量的Field,才可能考虑public修饰。除此之外,有些方法只是用于辅助实现该类的其他方法,这些方法被称之为工具方法,工具方法也应该用private修饰。
如果某个类主要用于其他类的父类,该父类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应当使用protected修饰。
希望暴露出来给其他类*调用的方法应该使用public修饰,因此大部分外部类都是用publci修饰。
访问控制和继承
请注意以下方法继承的规则:
父类中声明为public的方法在子类中也必须为public。
父类中声明为protected的方法在子类中要么声明为protected,要么声明为public。不能声明为private。
父类中声明为private的方法,不能够被继承。
实现封装
掌握了访问修饰符的用法之后,下面通过合理的访问控制符来定义一个Person类,这个Person类实现了良好的封装。
public class Person
{
private String name;
private int age;
public Person()
{
}
public Person(String name , int age)
{
this.name = name;
this.age = age;
}
public void setName(String name)
{
//执行合理性校验,要求用户名必须在2~6位之间
if (name.length() > 6 || name.length() < 2)
{
System.out.println("您设置的人名不符合要求");
return;
}
else
{
this.name = name;
}
}
public String getName()
{
return this.name;
}
public void setAge(int age)
{
//执行合理性校验,要求用户年龄必须在0~100之间
if (age > 100 || age < 0)
{
System.out.println("您设置的年龄不合法");
return;
}
else
{
this.age = age;
}
}
public int getAge()
{
return this.age;
}
}
定义了Person类之后,该类的age和name两个Field只有在Person类内才可以操作和访问,在Person类之外只能通过各自对应的setter和getter方法来操作和访问它们。
通俗地讲,get方法称为读取器,而set方法称为设置器。
get方法的语法格式为:
public returnType getPropertyName()
如果返回值类型是boolean型,习惯上如下定义get方法:
public boolean isPropertyName()
set方法的语法格式为:
public void setPropertyName(dateType propertyValue)
下面程序在main方法中创建一个Person对象,并尝试操作和访问该对象的age和name两个Field。
public class TestPerson
{
public static void main(String[] args)
{
Person p = new Person();
//因为age属性已被隐藏,所以下面语句将出现编译错误。
//p.age = 1000;
//下面语句编译不会出现错误,但运行时将提示出入的age属性不合法
//程序不会修改p的age属性
p.setAge(1000);
//访问p的age属性也必须通过其对应的getter方法
//因为上面从未成功设置p的age属性,故此处输出0
System.out.println("未能设置age属性时:" + p.getAge());
//成功修改p的age属性
p.setAge(30);
//因为上面成功设置了p的age属性,故此处输出30
System.out.println("成功设置age属性后:" + p.getAge());
//不能直接操作p的name属性,只能通过其对应的setter方法
//因为"李刚"字符串长度满足2~6,所以可以成功设置
p.setName("李刚");
System.out.println("成功设置name属性后:" + p.getName());
}
}
正如上面程序中的注释所言,PersonTest的main方法不可直接修改Person对象的age和name,只能通过各自对应的setter方法来操作这两个Field的值。