6、封装

面向对象设计中的一个基本问题:“如何区分变动的事物和不变的事物”。

这个问题对于类库而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。

为了解决这一问题,Java 提供了访问修饰符供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从"最大权限"到"最小权限"依次是:public,protected,包访问权限(没有关键字)和 private。

1、包的概念

如何将类库组件捆绑到一个内聚到类库单元中?
Java 中通过 package 关键字加以控制,类是在相同包下还是不同包下会影响访问修饰符。

使用 import 关键字。如果需要导入某个类,就需要在 import 语句中声明。

一个 Java 源代码文件称为一个编译单元(有时也称翻译单元)。每个编译单元的文件名后缀必须是 .java。在编译单元中可以有一个 public 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 .java)。每个编译单元中只能有一个 public 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 public 类,此时它们支持主 public 类。

2、代码的组织

当编译一个 .java 文件时,.java 文件的每个类都会有一个输出文件。每个输出的文件名和 .java 文件中每个类的类名相同,只是后缀名是 .class。

在 Java 中,可运行程序是一组 .class 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 jar 文档生成器)。Java 解释器负责查找、加载和解释这些文件。

如果把这些组件集中在一起,就需要使用关键字 package。

Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同

3、创建独一无二的包名

一个包从未真正被打包成单一的文件,它可以由很多 .class 文件构成。

将所有的文件放在一个子目录还解决了其他的两个问题:创建独一无二的包名和查找可能隐藏于目录结构某处的类。这是通过将 .class 文件所在的路径位置编码成 package 名称来实现的。按照惯例,package 名称是类的创建者的反顺序的 Internet 域名。

把 package 名称分解成你机器上的一个目录,所以当 Java 解释器必须要加载一个 .class 文件时,它能定位到 .class 文件所在的位置。首先,它找出环境变量 CLASSPATH(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。CLASSPATH 包含一个或多个目录,用作查找 .class 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(包名 foo.bar.baz 变成 foo\bar\baz 或 foo/bar/baz 或其它,取决于你的操作系统)。然后这个路径与 CLASSPATH 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 .class 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。

但是在使用 JAR 文件时,有点不一样。你必须在类路径写清楚 JAR 文件的实际名称,不能仅仅是 JAR 文件所在的目录。因此,对于一个名为 grape.jar 的 JAR 文件,类路径应包括:

CLASSPATH=.;D\JAVA\LIB;C:\flavors\grape.jar

4、定制工具库

以一个 CLASSPATH 位置开始

当创建一个包时,包名就隐含了目录结构。这个包必须位于包名指定的目录中,该目录必须在以 CLASSPATH 开始的目录中可以查询到。

5、使用 import 改变行为

条件编译还有其他的用途。调试是一个很常见的用途,调试功能在开发过程中是开启的,在发布的产品中是禁用的。可以通过改变导入的 package 来实现这一目的,修改的方法是将程序中的代码从调试版改为发布版。这个技术可用于任何种类的条件代码。

6、访问权限修饰符

Java 访问权限修饰符 public,protected 和 private 位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象。

如果不提供访问修饰符,就意味着"包访问权限"。所以无论如何,万物都有某种形式的访问控制权。

6.1、包访问权限

默认访问权限没有关键字,通常被称为包访问权限(有时也称为 friendly)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 private 的。

取得对成员的访问权的唯一方式是:

  1. 使成员成为 public。那么无论是谁,无论在哪,都可以访问它。
  2. 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。
  3. 继承的类既可以访问 public 成员,也可以访问 protected 成员(但不能访问 private 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。
  4. 提供访问器(accessor)和修改器(mutator)方法(有时也称为"get/set" 方法),从而读取和改变值。

6.2、public: 接口访问权限

当你使用关键字 public,就意味着紧随 public 后声明的成员对于每个人都是可用的。

如果几个类在相同的目录中且没有给自己设定明确的包名,Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。

6.3、private: 你无法访问

关键字 private 意味着除了包含该成员的类,其他任何类都无法访问这个成员。

使用 private 是非常重要的,尤其是在多线程环境中。

class Sundae {
    private Sundae() {}//构造器用private修饰
    static Sundae makeASundae() {
        return new Sundae();
    }
}

public class IceCream {
    public static void main(String[] args) {
        //- Sundae x = new Sundae();
        Sundae x = Sundae.makeASundae();
    }
}

然而,不能因为类中某个对象的引用是 private,就认为其他对象也无法拥有该对象的 public 引用(参见附录:对象传递和返回)

6.4、protected: 继承访问权限

有时,基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就需要使用 protected。protected 也提供包访问权限,也就是说,相同包内的其他类可以访问 protected 元素。

6.5、包访问权限 Vs Public 构造器

在一个具有包访问权限的类中定义一个 public 的构造器并不能真的使这个构造器成为 public,在声明的时候就应该标记为编译时错误。

7、接口和实现

访问控制通常被称为实现的隐藏。
将数据和方法包装进类中并把具体实现隐藏被称作是封装。其结果就是一个同时带有特征和行为的数据类型。

访问控制在数据类型内部划定了边界,基于以下原因:

  • 确立客户端程序员可以使用和不能使用的边界。可以在结构中建立自己的内部机制而不必担心客户端程序员偶尔将内部实现作为他们可以使用的接口的一部分。
  • 将接口与实现分离。如果在一组程序中使用结构,而客户端程序员只能向 public 接口发送消息的话,那么就可以自由地修改任何不是 public 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码。

为了清晰起见,你可以采用一种创建类的风格:public 成员放在类的开头,接着是 protected 成员,包访问权限成员,最后是 private 成员。这么做的好处是类的使用者可以从头读起,首先会看到对他们而言最重要的部分(public 成员,因为可以从文件外访问它们),直到遇到非 public 成员时停止阅读

8、类访问权限

  1. 每个编译单元(即每个文件)中只能有一个 public 类。这表示,每个编译单元有一个公共的接口用 public
    类表示。该接口可以包含许多支持包访问权限的类。一旦一个编译单元中出现一个以上的 public 类,编译就会报错。
  2. public 类的名称必须与含有该编译单元的文件名相同,包括大小写。所以对于 Widget 来说,文件名必须是Widget.java,不能是 widget.java 或 WIDGET.java。再次强调,如果名字不匹配,编译器会报错。

当你创建了一个包访问权限的类,把类中的属性声明为 private 仍然是有意义的——应该尽可能将所有属性都声明为 private,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。

注意,类既不能是 private 的(这样除了该类自身,任何类都不能访问它),也不能是 protected 的。所以对于类的访问权限只有两种选择:包访问权限或者 public。为了防止类被外界访问,可以将所有的构造器声明为 private,这样只有你自己能创建对象(在类的 static 成员中)

// hiding/Lunch.java
// Demonstrates class access specifiers. Make a class
// effectively private with private constructors:

class Soup1 {
    private Soup1() {}

    public static Soup1 makeSoup() { // [1]
        return new Soup1();
    }
}

class Soup2 {
    private Soup2() {}

    private static Soup2 ps1 = new Soup2(); // [2]

    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();
    }
}

可以像 [1] 那样通过 static 方法创建对象,也可以像 [2] 那样先创建一个静态对象,当用户需要访问它时返回对象的引用即可。Soup2 用到了所谓的设计模式。这种模式叫做单例模式,因为它只允许创建类的一个对象。

如果你不显式地创建构造器,编译器会自动为你创建一个无参构造器(没有参数的构造器)。如果我们编写了无参构造器,那么编译器就不会自动创建构造器了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值