Java语言GUI程序设计
上QQ阅读APP看书,第一时间看更新

4.2 类封装与访问控制

封装是面向对象程序设计技术的三大基本特征之一。通过封装,对象对外部隐藏了它的状态和行为的具体实现,使对象得以保持独立性。Java语言提供了多个关键字对域变量和方法的访问范围进行限制,以便实现不同程度的封装。

4.2.1 类封装

类是对现实世界实体的抽象,将抽象出来的状态和行为结合在一个封装的整体里,这个封装体就是类。类的属性在代码中以域变量的方式表示,类的对象各个属性的当前值就是对象的状态;外部可见的对象活动就是对象的行为,类中定义的方法是针对其对象实施的操作或服务,也就是说,方法用来实现对象的行为(参见图4.8)。对象的状态通过其行为改变,也就是属性值通过调用方法来改变。

图4.8 对象的状态和行为抽象为类的属性和方法

类的设计应该将对象的状态和行为的真实内部实现细节隐藏起来,禁止外部直接访问它的状态信息,而在类的内部定义对其对象的状态实施访问、操作和管理的方法,对外界提供和保持一个定义良好的接口。

开发中的一个Java程序是由若干个类组成的。面向对象程序设计方法要求类的设计应该做到最大化封装和最小化耦合。最大化封装是指每个类的设计越独立越好,每个类不应该对它的任何内部属性提供对象之间的直接访问,例如一个对象不能直接修改另外一个对象的属性值;类应该向外界提供能实现其职责的最少数目的方法,向外界提供的接口应该尽量少受类内部设计变化的影响。最小化耦合是指一个类应该尽可能地通过自身定义的方法实施对其对象的操作和管理,只依赖于其他类的公有接口而不依赖于其他类内部实现与其交互;如果类之间要实现共同职责而必须耦合,也必须把这种耦合对外界的影响降到最低。

运行中的Java程序是由一些互操作的对象构成的。一个对象发送消息给其他对象实施消息传递或动作请求,从而使对象之间进行互操作,在Java程序中对象通过调用其他对象的方法互操作得到处理。

4.2.2 访问控制

Java程序设计对类中定义的属性和方法的使用范围需要进行限制,甚至也需要对类的使用范围进行限制,这种限制是通过使用Java语言的访问修饰符实现的。访问修饰符也是实现类的封装性的工具。

1. public修饰符

使用public修饰符的属性和方法既可以在定义它们的类中使用,也可以在任何其他类中使用。一般地,需要提供给任意其他类使用的公共方法都应该采用public修饰符进行限定。例如,例4.3设计的User类中自动生成的构造方法、getter和setter方法、hashCode和equals方法都使用public修饰(见程序清单4.1),它们都是使用User类时的接口方法。类的对象大多数情况下需要在别的类中创建,因此类的构造方法基本都采用public修饰。在UML类图中public方法前面以+标记。

一些属性也需要提供给外界任意类使用,例如,Integer类中的MIN_VALUE、MAX_VALUE等属性。在UML类图中public字段前面以+标记。

当修饰符public用于修饰一个类的定义时,指明这个类可以在任何类中使用。应注意,Java 9新引入的模块化系统对public类的访问性进行控制,即类是存在于模块中的,模块必须明确声明其中哪些公共类可以被其他模块访问。除非模块明确地使其公共类可访问,否则其他模块就不能访问另一个模块中的公共类。

一个Java源程序文件是一个编译单元,只能有一个public类,但可以有多个没有public修饰的类,文件名必须与public类的类名完全相同,甚至文件名字母的大小写也必须与类名保持一致,文件扩展名是.java。一个Java源程序文件中也可以没有public类,此时文件基本可以随意取名,扩展名仍然是.java。

2. 包访问性

包(Package)既是解决类、变量和方法名字冲突的机制,也是访问控制的工具。

许多情况下,Java程序会在Internet中运行,类在程序运行时自动下载,因此需要防范两个封装了不同类型对象的类使用相同的名字从而发生冲突的情况,办法是将类定义在包中。包采用域名倒序方法取名,而域名在Internet上是唯一的,这样就可以保证带有包名的类名也是唯一的。

同一个包中的类具有访问的友好性,即如果类名、属性名和方法名前面不加任何访问修饰符就是默认访问控制,此时当前包内的其他所有类都能访问它们,但包外的所有类都不能访问。利用这种机制,将相关的类都组合到一个包里,可以使它们相互之间方便地进行沟通。

例如,例4.2中的两个类ObjSwap和TwoInt都位于同一个包book.methoddemos中,因此在ObjSwap类中可以直接访问TwoInt类的对象var的域变量var1和var2。

3. private修饰符

使用private修饰的属性和方法只能在其所在的类中使用,任何其他类都不能直接访问它们。因此,private修饰符是实现类封装和代码隐藏的主要工具。例如,例4.3中尽管步骤(2)添加字段name、password和job时没有使用任何访问修饰符,但是在步骤(4)“生成getter和setter”对话框中选择了“封装字段”复选框,因此NetBeans IDE自动为这三个属性添加了private修饰符以封装它们。

如果类的属性需要在该类之外访问,通常还是将该属性定义为private访问性以符合封装的原则,同时为该属性提供具有public访问性的取值方法(也称访问器或getter)和设值方法(也称修改器或setter)。取值方法名以get开头,设值方法以set开头,后面跟属性名,且属性名的第一个字母大写。取值方法返回值与属性类型相同;设值方法无返回值,但需要定义与属性类型相同的参数。例如,例4.3中步骤(4)为User类的字段name、password和job自动生成的取值方法和设值方法就是按照这个惯例编码的。

在UML类图中private字段和方法前面以-标记。

4. protected修饰符

可以以现有的类为父类定义一个新类,这个新类是该现有类的子类,详细内容4.3节介绍。

一个类中使用protected修饰符的属性和方法既可以被同一个包中的其他类直接访问,也可以被不同包中该类的子类直接访问。

Java语言的访问修饰符及其对类成员访问控制的总结如表4.1所示。

表4.1 访问控制表

4.2.3 static修饰符

一般情况下,类中定义的域变量和方法是属于具体对象的,例如,User类中的字段name、password和job是一个具体用户的姓名、密码和身份,描述所有用户的User类不可能有一个具体的姓名、密码和身份。因此,对于类中定义的域变量和方法总是需要通过具体对象访问和调用。如果在User类中设置一个字段id用来对用户编号,且每个用户都有一个唯一编号,那么这个id字段是所有用户公用的,不能被任何一个用户控制,而是需要被User类统一控制。类的这种对于所有对象公用的字段,Java语言使用static修饰符定义,说明它是类变量,属于整个类。类的对象也称为这个类的一个实例,相应地将属于具体对象的域变量称为实例变量。

对于static域变量的操作应该在属于整个类的方法中进行,这种方法也使用static进行修饰。不能在非static方法中直接访问static域变量。

static域变量和方法称为静态成员,对它们的访问使用类名进行,即采用以下格式访问:

     类名.静态字段名
     类名.静态方法名(实参表)

这种用法在前面的例题程序中十分普遍。例如,例3.1的IfMaxDemo类中第13行语句“int maxNum=Integer.MIN_VALUE;”引用的就是Integer类中的静态域MIN_VALUE(此处是常量),多个例题程序调用Integer类的静态方法parseInt(Integer.parseInt(text))。显然,不用创建对象就可以使用类中的域变量和方法,程序显得更为简洁。

自Java 5开始,还增加了一个静态导入语句,即在import关键字之后紧接着一个static关键字,在导入的类名之后添加一个句点和一个星号或一个静态方法名。即静态导入语句的格式是:

     import static 包名.类名.*;

     import static 包名.类名.静态方法名;

然后就可以直接用方法名调用静态方法,而不必前面缀有类名。这种用法在一个静态方法在程序中大量使用时可以减少输入,但是要小心避免方法名的冲突。

例4.4 例4.3设计的用户封装类User添加用户编号字段id,使每个用户都有一个唯一编号。

解:按照以下步骤操作。

(1)在User类中添加字段id。方法是在User类中添加语句“private static int id;”。

(2)为字段id添加访问器和修改器。方法与例4.3步骤(4)相同。完成操作后添加的源代码如下。

(3)在构造方法中添加语句“id++;”,使程序在每创建一个User对象时编号增加,避免不同用户的编号发生重复。