- 访问控制( 或隐藏具体实现 )与最初的实现并不恰当 有关。
- Java提供了访问修饰词,以供类库开发人员向客户端程序员指明那些是可用的,那些是不可用的。访问权限控制的等级,从最大权限到最小的权限依次为: public , protected , 包访问权限(没有关键词) 和 private 。
包: 库单元
- 包内包含一组类,它们在单一的名字空间之下被组织在了一起。
public class SimpleEnumUse {
public static void main(String[] args) {
java.util.ArrayList arrayList=new java.util.ArrayList();
}
}
- 这立刻使程序变得很冗长,因此 你可能想转试用 import 关键字。如下
import java.util.ArrayList;
public class SimpleEnumUse {
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
}
}
- 现在可以不用限定地使用 ArrayList了,但是, 这样做 java.util 中的其他类仍旧是都不可用的。要想导入其中所有的类,只需使用 * ,如下
import java.util.*;
- 我们之所以要导入, 就是要提供一个管理名字空间的机制。所有类成员的名称都是批次隔离的。
- 在Java中对名称空间进行完全控制并为每个类创建唯一的标识符组合就成为了非常重要的事情。
代码组织
- Java可运行程序是一组可以打包并压缩为一个Java文档文件(JAR ,使用Java的jar文档生成器)的 .class 文件。Java解释器负责这些文件的查找,装载 和解释。
- 类库实际上是一组类文件。 其中每个文件都有一个 public 类,以及任意数量的非public 类, 因此每个文件都有一个构件。这就要使用 package
//如果使用 package 它必须处于 文件中除注释以外第一句程序代码
package access;
- 就表示你在声明该编译单元是名为 access 的类库的一部分。或者换句话说,你正在声明该编译单元中的public 类名称是位于 access 名称的保护伞下。
- 任何想要使用该名称的人都必须使用前面给出的选择,指定全名或者与 access 结合使用关键字 import。
- package 和 import关键字允许你做的,是将单一的全局名字空间分隔开,使得无论多少人使用 Internet以及 Java开始编写类, 都不会出现名称冲突问题。
创建独一无二的包名
- 将所有的文件收入一个子目录还可以解决另外两个问题:
- 通过将 .class 文件所在的路径位置编码成 package 的名称来实现,也就是 反顺序自己的Internet域名
- 把package 名称分解为你机器上的一个目录。当Java程序运行并需要加载 .class 文件的时候,它就可以确定 .class 文件在目录上所处的位置。
- Java解释器运行过程:
- 找出环境变量 CLASSPATH ,CLASSPATH包含一个或者多个目录,用作查找 .class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换为反斜杠,以从 CLASSPATH 根中产生一个路径名称。得到的路径会与CLASSPATH 中的各个不同的项相连接, 解释器就在这些目录中查找与你所要创建的类名称相关的 .class 文件。
用 Import 改变行为
- Java 没有C的条件编译功能,该功能可以使你不必要更改任何程序代码,就能够切换开关并产生不同的行为。
- 条件编译还有一些有价值的用途。调试就是一个很常见的用途。调试功能在开发过程中是开启的,而在发布的产品中是禁用的。可以通过修改被导入的 package 的方法来实现这一目的,修改的方法是将你程序中用到的代码从调试版改为发布版本。
对使用包的忠告
- 无论何时创建包,都必须在给定包的时候隐含地指定了目录结构。
- 注意: 编译过的代码通常放置在与源代码的不同目录中,但是必须保证 JVM 使用 CLASSPATH 可以找到该路径。
Java 访问权限修饰词
- public , protected 和 private 这几个Java访问权限修饰词在使用时,是置于类中每个成员的定义之前的__无论它是一个域还是一个方法。每个访问权限修饰词仅控制它所修饰的特定定义的访问权。
- 如果不提供任何访问权限修饰词,则意味着它是 包访问权限 。因此,无论何时,所有事物都具有某种形式的访问权限控制。
包访问权限
- 默认访问权限没有任何关键字,但通常是指包访问权限(有时也表示为 friendly)。这就意味着当前的包中的所有其他类对成员都有访问权限,但对于这个包之外的所有类,这个成员却是 private。由于一个编译单元(即一个文件),只能隶属于一个包,所以经由包访问权限,处于同一编译单元中的所有类彼此之间都是自动可访问的。
- 在Java中,则要强制你以一种合理的方式对它们加以组织。另外,你可能还想要排除这样的类__它们不应该访问在当前包中所定义的类。
public : 接口访问权限
- 使用关键字 public :就意味着 public 之后紧跟着的成员声明自己对每个人都是可用的,尤其是使用类库的客户程序员更是如此。
package com.spring.main.demo;
public class SimpleEnumUse {
public SimpleEnumUse(){
System.out.println("SimpleEnumUse 构造器");
}
void bite(){
System.out.println("bite");
}
}
- SimpleEnumUse.java 文件必须处于 demo的子目录中,该子目录在 com.spring.main下, 而c05 则必须位于 CLASSPATH指定的众多路径的其中之一的下边。
//创建一个 SimpleEnumUse 对象
import com.spring.main.demo.*;
public class Dinner {
public static void main(String[] args) {
SimpleEnumUse enumUse = new SimpleEnumUse();
//enumUse.bite(); 无法访问
}
}
//运行结果为
SimpleEnumUse 构造器
- 就可以创建一个 SimpleEnumUse对象,因为它的构造器是 public 而且类也是 public的。但是, 由于 bite()只向 demo 包中的类提供访问权,所以 bite()成员在 Dinner之中是无法访问的,因此编译器也禁止你使用它。
默认包
- 下面程序代码看起来破坏了上述规则,但它仍可以编译:
package com.access;
class Pie {
void f(){
System.out.println("Pie f()方法");
}
}
//第二个类
package com.access;
class Cake {
public static void main(String[] args) {
Pie pie = new Pie();
pie.f();
}
}
//运行结果为
Pie f()方法
- 最初或许你认为这俩个文件毫不相关,但是 Cake却可以创建一个 Pie对象并调用它的 f()方法, 通常会认为 Pie 和 f() 享有包访问权限,因而是不可以为 Cake所用的。
- 它们的确享有包访问权限,但这只是部分正确的。Cake.java 可以访问它们的原因是因为它们同处于相同的目录并且没有给自己设定任何包名称。
- Java将这样的文件自动看做隶属于该目录的默认包之中,于是它们为该目录中所有其他的文件都提供了包访问权限。
private: 你无法访问
- private: 除了包内该成员的类之外,其他任何类都无法访问这个成员。由于处于同一包内的其他类是不可以访问 private成员的,因此这就等于说自己隔离了自己。
- 从另一方面来说,让许多人共同合作来创建一个包也是不大可能的,为此 private 就允许你随意改变该成员,而不必考虑这样做是否会影响到包内其他的类。
- 默认包访问权限已经提供了充足的隐藏措施。请记住,使用类的客户端程序员是无法访问包访问权限成员的。
package com.access;
class Pie {
private Pie(){}
static Pie createObject(){
return new Pie();
}
}
//第二个测试类
package com.access;
class Cake {
public static void main(String[] args) {
Pie pie = Pie.createObject();
// Pie pie = new Pie();
}
}
- 这是一个说明 private 终有用武之地的示例, 可能想控制如何创建对象,并阻止别人直接访问某个特定的构造器(或全部构造器)。
- 在例子中,不能通过构造器来创建 Pie 对象,而必须调用 createObjet方法来达到此目的。
- 任何可以肯定只是该类一个助手方法的方法,都可以把它定为 private,确保不会再包内的其他地方误用到它,于是也就防止了你会去改变或删除这个方法。将方法指定为 private 确保了你拥有这种选择权。
- 对于类中的 private 域同样适用。除非必须全部公开底层实现细目,否则就应该将所有的域指定为 private。
protected: 集成访问权限
- protected : 通过继承可以利用一个现有类__我们将其称为基类,然后将新成员添加到该现有类中而不必碰该现有类。
- 还可以改变现有成员的行为。为了从现有类继承,需要声明新类 extends (扩展)了一个现有类,如下
class Foo extends Bar{}
- 类定义中的其他部分看起来都是一样的。
package com.access;
public class Pie {
public Pie(){
System.out.println("pie构造器已经执行");
}
protected void createObject(){
System.out.println("createObject");
}
}
//第二个类
package com.access;
public class Cake extends Pie{
public Cake(){
System.out.println("Cake container");
}
public void chomp(){
createObject();
}
public static void main(String[] args) {
Cake cake = new Cake();
cake.chomp();
}
}
//运行结果
pie构造器已经执行
Cake container
createObject
- 尽管 createObject()也具有包访问权限,但是它仍旧不是 public的。
接口和实现
- 访问权限的控制常被称为 具体实现的隐藏。
- 把数据 和方法 包装进类中,以及具体实现的隐藏,常共同称作 封装。其结果是一个同时带有特征和行为的数据类型。
- 出于两个很重要的原因,访问权限控制将权限的边界划在了数据类型的内部。
- 是要设定客户端程序员可以使用和不可以使用的界限。可以在结构中建立自己的内部机制,而不必担心客户端程序员会偶然地将内部机制当做它们可以使用的接口的一部分。
- 即将接口和具体实现进行分离。如果结构是用于一组程序之中,而客户端程序员除了可以向接口发送信息之外也不可以做的话,那么就可以随意更改所有不是public 的东西,从而破坏客户端代码。
类的访问权限
- 在Java中,访问权限修饰词也可以用于确定库中的那些类对于该库的使用者是可用的。
public class Widget{}
//如果库的名字是 access 那么任何客户端程序员都可以通过下面的声明访问 Widght
import access.Widget;
//或者
import access.*;
- 每个编译单元(文件)都只能有一个 public 类。每个编译单元都有单一的公共接口,用 public 类来表现。
- public类的名称必须完全与含有编译单元的文件名想匹配,包括大小写。所以对于 Widget而言,文件的名称必须是 Widget.java而不是 widget.java 或者 WIDGET.java 。如果不匹配,同样将得到编译时错误。
- 虽然不是很常用,但编译单元内完全不带 public 类也是可能的。在这种情况下,可以随意对文件命名。
- 请注意 : 类不可以是 private 的,也不可以是 protected的
- 所以对于类的访问权限,仅有俩个选择 :包访问权限 或 public
总结
- 本章讨论了类是如何被构建成类库的:首先,介绍了一组类是如何被打包到一个类库中的,其次,类是如何控制其成员的访问的。
- 控制对成员的访问权限有两个原因:
- 为了使用户不要碰触那些他们不该碰触的部分,这些部分对于类内部的操作是必要的,但是它并不属于客户端程序员所需接口的一部分。
- 为了让类库设计者可以更改类的内部工作方式,而不必担心这样会对客户端程序员产生重大的影响。
- 注意:
- 访问权限控制专注于类库创建这和该类库的外部使用者之间的关系,这种关系也是一种通信方式。