在前面的例子中,对于方法和成员变量几乎没有进行访问控制。Java对方法和成员变量提供了3个修饰符用于权限控制:public、protected和private。
public修饰符表示类中的一个方法或成员变量是公有的。公有的方法或成员变量可以被其他任何类访问,无论这些类和它所在的类是否在同一个包中。公有的访问控制是最宽松的访问控制,只要一个方法或成员变量声明为公有,那么任何类中的方法都可以访问它。
用public修饰的域称为公共域。如果公共域属于一个公共类,则它可以被其他所有类所引用。由于public 修饰符会降低运行的安全性和数据的封装性,所以一般应减少public域的使用。
为了说明public的访问范围,这里首先编写一个具有公共域和公共方法的类public FooClass,并且把这个类打包成package1,如下所示。
package package1;
public class publicFooClass{
public int publicVar = 1;
public void publicMehod(){
System.out.println("Hello, I am in package1.");
}
}
将这个文件命名为publicFooClass.java。
然后编写一个publicBarClass类,打包成package2,并且将这个文件命名为Public BarClass. java。这样,publicFooClass和publicBarClass分属两个不同的包。
注意在文件publicBarClass中,类名应该写全名,即带包名的类名,或者使用import语句:
import package1.publicFooClass;
引入package1的publicFooClass类。
把这两个文件放在同一个目录下,然后首先编译publicFooClass.java,如下所示。
javac –d . publicFooClass.java
这个编译命令将在当前目录下生成package1的子目录,并且把编译出来的publicFoo Class.class放在这个子目录下。
然后用同样的方法编译publicBarClass.java,如下所示。
javac –d . publicBarClass.java
同样,这个编译命令将在当前目录下生成package2的子目录,并且把编译出来的publicBar Class.class放在这个子目录下。
然后运行publicBarClass,如下所示。
java package2.publicBarClass
运行结果如下。
foo.publicVar = 1
foo.publicVar = 2
Hello, I am in package1.
从上述例子可以看到,public方法和public域能被其他包中的类所访问。
一个声明为protected的方法或者成员变量,可以在其所在的包中被任意地访问,但是在声明它的包以外,则只有它所在的类的子类才能够访问它,其他与其所在的类没有继承关系的类不能访问它。即用protected 修饰的成员变量可以被3种类所引用:该类自身;与它在同一个包中的其他类;在其他包中的该类的子类。
protected访问权限将类中方法和域的访问权限限制在类及其子类中。由于和其他的类相比,一个类的子类与这个类有着更亲密的关系,所以在访问权限上,一个类的子类可以比其他类享有更多的权利。这样做主要是基于以下的考虑:子类通常更加清楚地知道超类的内部细节;创建子类的程序员通常拥有超类的源代码;子类通常对超类进行修改,以增加或增强超类的某些功能,因此,它常常需要访问超类的方法和成员变量。
【例4-5】本例程序说明了用protected修饰的域和方法能够被本包中的类所访问,程序代码如下:
//文件protectedFooClass
package package1;
public class protectedFooClass{
protected int protectedVar = 1;
protected void protectedMethod(){
System.out.println("protectedVar = " + protectedVar);
}
public void wrapper(){
protectedVar ++;
protectedMethod();
}
}
//文件protectedFoo
package package1;
public class protectedFoo{
public static void main(String args[]){
protectedFooClass foo = new protectedFooClass();
System.out.println("foo.protectedVar: " + foo.protectedVar);
foo.protectedVar ++;
foo.protectedMethod();
}
}
编译和运行这个程序,如下所示。
D:\>javac -d . protectedFooClass.java
D:\>javac -d . protectedFoo.java
D:\>java package1.protectedFoo
结果如下。
foo.protectedVar: 1
protectedVar = 2
下面编写一个类,并把这个类打包在package2中,观察在其他包中的类对用protected修饰的域和方法的访问权限如何,代码如下。
package package2;
import package1.protectedFooClass;
public class protectedBar{
public static void main(String args[]){
protectedFooClass foo = new protectedFooClass();
foo.protectedVar ++;
foo.protectedMethod();
}
}
编译这个程序,结果如下。
protectedBar.java:7: protectedVar has protected access in package1. prot-
ectedFooClass
foo.protectedVar ++;
^
protectedBar.java:8:protectedMethod() has protected access in package1. protectedFooClass
foo.protectedMethod();
^
2 errors
可以看到,用protected修饰的域和方法能够被同一个包中的其他类所访问,而不能被其他包中的其他类访问。但是,某个类的用protected修饰的域和方法能被其他包中的这个类的子类所访问,代码如下。
package package2;
import package1.protectedFooClass;
class subFooClass extends protectedFooClass{
public void wrapper(){
protectedVar ++;
protectedMethod();
}
}
public class protectedBar2{
public static void main(String args[]){
subFooClass foo = new subFooClass();
foo.wrapper();
}
}
编译运行这个程序,输出如下。
protectedVar = 2
最后一种访问权限是private,即一个方法或成员变量被声明为私有的,表示这个方法或成员变量只能被它所在的类中的方法所访问,而不能被其他任何类访问,即使是它的子类也不能访问。私有的访问权限提供了最低的可见度和最高的保护性,它和公有访问权限完全相反。私有的访问控制是经常使用的。我们知道,面向对象的编程的一个基本思想就是封装,而私有的访问控制就提供了最好的封装:除了它所在的类以外,没有任何其他的类能够访问它。一个类的私有数据、内部状态、用于内部实现的数据都应该声明为私有的,它们是不应该被外部的类(甚至它的子类)知道的信息。
【例4-6】本例程序说明了private的访问范围。
class privateFooClass{
private int privateVar = 1;
private void privateMethod(){
System.out.println("privateVar = " + privateVar);
}
public void wrapper(){
privateVar ++;
privateMethod();
}
}
class subFooClass extends privateFooClass{
public void subWrapper(){
privateVar ++;
privateMethod();
}
}
public class privateFoo{
public static void main(String args[]){
subFooClass foo = new subFooClass();
foo.wrapper();
foo.subWrapper();
}
}
编译这个程序,会得到以下错误信息。
privateFoo.java:15: privateVar has private access in privateFooClass
privateVar ++;
^
privateFoo.java:16: privateMethod() has private access in privateFooClass
privateMethod();
^
2 errors
这说明即使是privateFooClass的子类也不能访问它的父类中的私有域或私有方法。
私有访问权限也可以用于构造方法。如果不希望创建一个类的实例,则可以将它的所有的构造方法都声明成private的。同样,如果不希望一个类的实例在其他包中被创建,则可以声明一个构造方法,以免编译器自动生成一个public权限的构造方法,然后将这个构造方法的访问权限声明成非public。
在大多数情况下,我们并不在方法或域的前面加任何修饰符,这时默认的访问权限是包(package)的,即只有和它所在的类属于同一个包中的类才有权访问它,而其他包中的类都无权访问它。
//文件defaultFooClass.java
package package1;
public class defaultFooClass{
int defaultVar = 1;
void defaultMethod(){
System.out.println("defaultVar = " + defaultVar);
}
public void wrapper(){
defaultVar ++;
defaultMethod();
}
}
//文件defaultBar.java
package package2;
import package1.defaultFooClass;
public class defaultBar{
public static void main(String args[]){
defaultFooClass foo = new defaultFooClass();
foo.defaultVar ++;
foo.defaultMethod();
}
}
编译defaultBar.java,将产生如下编译错误。
defaultBar.java:7: defaultVar is not public in package1.defaultFooClass; cannotbe accessed from outside package
foo.defaultVar ++;
^
defaultBar.java:8: defaultMethod() is not public in package1.default-
FooClass; cannot be accessed from outside package
foo.defaultMethod();
^
2 errors
可见,这两个错误都是由于越包访问造成的。一个类的用protected修饰的域和方法能被其他包中这个类的子类所访问,而默认的访问权限却不可以,如以下代码所示:
package package2;
import package1.defaultFooClass;
class subFooClass extends defaultFooClass{
public void wrapper(){
defaultVar ++;
defaultMethod();
}
}
public class defaultBar2{
public static void main(String args[]){
subFooClass foo = new subFooClass();
foo.wrapper();
}
}
编译这个文件将得到以下错误信息。
defaultBar2.java:6: defaultVar is not public in package1.defaultFooClass; cannot be accessed from outside package
defaultVar ++;
^
defaultBar2.java:7:defaultMethod()is not public in package1.default FooClass;cannot be accessed from outside package
defaultMethod();
^
2 errors
对于类中的实例变量,通常需要把它们声明为private,常量除外。如果一个实例变量不被声明为private,那么就有可能产生一些问题。
首先,如果希望一个实例变量是只读的,但是又没有把它声明成private,那么这个类以外的其他的方法也可以访问并修改它,结果造成此实例变量值的混乱,如以下一段代码所示:
public class Foo{
boolean available;
……
}
在其他的类中,可能有这样的语句:
Foo foo = new Foo ();
foo.available = false;
这样,在这个类以外就不正常地改变了这个实例变量的值,结果有可能造成程序的错误或崩溃。
由于Java并没有提供对实例变量的只读或只写的保护,因此,为了保护实例变量不被错误地改写,需要将它们声明为private,然后提供合适的方法来获得它的值,如下面一段代码所示。
public class Foo{
boolean available;
public boolean isAvailable(){
return available;
}
……
}
经过这样的处理,在其他的类中就不能任意修改实例变量available的值,但是可以通过这个类提供的方法isAvailable( )来获取实例变量available的值。
使用方法来存取实例变量的值是面向对象的编程中经常使用的思想。尽管这样做可能会花费一些时间,但是它所带来的好处是明显的。使用方法来存取实例变量无疑会减少错误的发生,使程序更加健壮,同时还会提高程序的可复用性。这样的方法称为存取方法,因为它们的唯一用途就是读写这些私有的实例变量。
读取实例变量的方法通常是public,因为让其他Java代码读取实例变量并不会产生什么错误,实例变量的值不会被改变。但是给实例变量赋值的方法的访问权限却要商量考虑一下。首先,不能将它声明为public,那样其他的类可以任意地改变实例变量的值;然而,也不能将它声明为private,那样就等于没有定义这样的方法,因为除了这个类以外,没有类可以访问这个方法。通常我们将给实例变量赋值的存取方法声明为protected,这样只有一个类和它的子类以及和它在同一个包中的类才有可能调用这个方法来改变实例变量的值。如果这个变量是一个特殊的私有数据,也可以将给它赋值的方法声明为private,而将读取它的方法声明为protected,这样就将实例变量的访问范围缩得更小了。
存取方法的名称通常有这样的约定,即读取方法名为“get+变量名”,赋值方法名为“set+变量名”。这样便于记忆。
使用存取方法的另一个好处是提高程序的可复用性,而且修改起来更加方便。因为不是直接将实例变量提供给外部,而是外部类的方法通过存取方法来访问实例变量。这样,不仅隐蔽了实例变量的内部表示,而且通过存取方法提供给外部的可以是经过加工了的实例变量,从而提高了程序的灵活性。同时,在赋值方法中可以提供必要的数据检查以检查数据的合理性,从而增强了程序的健壮性。而且,如果需要改变实例变量的内部表示,则只需要改动它相应的存取方法就行了,因为所有对实例变量的访问都是通过它的存取方法进行的。