所有优秀的作者,包括那些编写软件的程序员,都清楚其著作的某些部分直至重新创作的时候才变得完美,有时甚至要反复重写多次。这就需要将变得和不变得代码区分开来,保证重新得时候不影响整体的运行。
权限访问修饰符以供类库开发人员向客户端程序员(调用类库代码的程序员)指明那些可用,那些不可用。访问权限依此为:public,protected,包访问权限(没有关键词),private。
Java会将构建捆绑到一个内聚的类库单元中(包)。使用关键字package控制,访问权限的修饰词会因为在同一个包还是单独的包收到影响。
6.1包:库单元
包内包含有一组类,他们在单一的名字空间下被组织在一起。包名是一个管理名字空间的机制,所有类成员的名字都是彼此隔离的。Java对名称空间进行完全控制,为每个名称空间(包)下的类创建唯一表示的名字。
每个编译单元里只有一个public类,而且这个类名需要和文件名完全相同,首字母大写,驼峰式。如果还有额外的类,都是包访问权限,主要是为public类提供支持的。
类库实际上是一组类文件。每个文件都有一个public类,和任意数量的非public类。如果希望这些构件属于一个群组,就需要使用package。
Java包名规则是全部小写。
package和import关键字是为了将单一的名字空间隔开。
无论何时创建包,都已经在给定包的名称的时候隐含地指定了目录结构,这个包必须位于其名称所指定的目录之下。
6.2java访问控制修饰词
public,protected,private这几个Java访问修饰词在使用的时候,置于类中的每个成员的定义之前的,无论是一个域(变量)还是一个方法。修饰它控制的域或方法的访问权。没有任何访问控制修饰词,默认是包访问权限。
包访问权限:
包中的所有其它类对那个成员都有访问权限,但对这个包之外的所有类,这个成员确实private。包访问权限,同一个编译单元(一般指同一个文件)中的所有类彼此直接都是可以访问的(在同一个文件下,肯定在同一个包中)。
获得成员访问权的途径:
使该成员成为public;放在同一个包中,不加访问权限符,默认包访问权限;继承自public或者protected;提供get和set方法。
public:接口访问权限
//: access/cookie2/Cookie.java
package access.cookie2;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
} ///:~
//: access/dessert/Cookie.java
// Creates a library.
package access.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void bite() { System.out.println("bite"); }
} ///:~
private:你无法访问
除了包含该成员的类之外,其他任何类都无法访问这个成员。
使用类的客户端程序员是无法访问包访问权限成员的,因为不可能在同一个包下。
控制如何创建对象,阻止别人直接访问某个特定的构造器(或者全部构造器):
//: access/IceCream.java
// Demonstrates "private" keyword.
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//! Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
} ///:~
protected:继承访问权限:
如果创建一个新包,并自另一个包中继承类,那么唯一可访问原包中的public类。把对自己的访问权限赋予派生类,就需要protected也提供包访问权限。
//: access/Dinner.java
// Uses the library.
import access.dessert.*;
public class Dinner {
public static void main(String[] args) {
Cookie x = new Cookie();
//! x.bite(); // Can't access
}
} /* Output:
Cookie constructor
*///:~
//: access/ChocolateChip.java
// Can't use package-access member from another package.
import access.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
System.out.println("ChocolateChip constructor");
}
public void chomp() {
//! bite(); // Can't access bite
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
x.chomp();
}
} /* Output:
Cookie constructor
ChocolateChip constructor
*///:~
//: access/cookie2/Cookie.java
package access.cookie2;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
} ///:~
//: access/ChocolateChip2.java
import access.cookie2.*;
public class ChocolateChip2 extends Cookie {
public ChocolateChip2() {
System.out.println("ChocolateChip2 constructor");
}
public void chomp() { bite(); } // Protected method
public static void main(String[] args) {
ChocolateChip2 x = new ChocolateChip2();
x.chomp();
}
} /* Output:
Cookie constructor
ChocolateChip2 constructor
bite
*///:~
上边是几个很典型很简单的例子。
6.3接口和实现
访问权限控制常被成为是具体实现的隐藏。把数据和方法包装进类,以及具体实现的隐藏,称作封装。
将接口和具体实现分离,类似于service和serviceImpl,controller只访问接口,实现类无论如何改都无所谓。
6.4类的访问权限
访问修饰符也可以用于确定库中的那些类对于该库的使用者是可用的。
每个编译单元只有一个public类,必须和文件名相同。
不建议这样做:文件中可以没有public类,文件名可以随意写,容易造成混乱。
如果不希望其他人对类有访问权限,可以把所有的构造器指定为private。但是在该类的static成员内部可以创建:
//: access/Lunch.java
// Demonstrates class access specifiers. Make a class
// effectively private with private constructors:
class Soup1 {
private Soup1() {}
// (1) Allow creation via static method:
public static Soup1 makeSoup() {
return new Soup1();
}
}
class Soup2 {
private Soup2() {}
// (2) Create a static object and return a reference
// upon request.(The "Singleton" pattern):
private static Soup2 ps1 = new Soup2();
public static Soup2 access() {
return ps1;
}
public void f() {}
}
// Only one public class allowed per file:
public class Lunch {
void testPrivate() {
// Can't do this! Private constructor:
//! Soup1 soup = new Soup1();
}
void testStatic() {
Soup1 soup = Soup1.makeSoup();
}
void testSingleton() {
Soup2.access().f();
}
} ///:~
可以用来记录到底创建了多少个Soup1对象。
Soup2是单力模式,只能创建一个对象。
6.5总结
让类库设计者可以修改内部代码,而不必担心会对客户端造成影响。
访问权限控制专注于类库创建者和该类库使用者之间的关系,这种关系也是一种通信关系。