本文是对《On Java 8》即《Java编程思想》第五版的知识点汇总整理,仅供学习分享。
类库开发者必须同意在修改类库中的一个类时,不会移除已有的方法,因为那样将会破坏客户端程序员的代码。与之相反的情况更加复杂。在有成员属性的情况下,类库开发者如何知道哪些属性被客户端程序员使用?这同样会发生在那些只为实现类库类而创建的方法上,它们也不是设计成可供客户端程序员调用的。如果类库开发者想删除旧的实现,添加新的实现,结果会怎样呢?任何这些成员的改动都可能破环客户端程序员的代码。因此类库开发者会被束缚,不能修改任何事物。
为了解决这一问题,Java 提供了访问修饰符(access specifier)供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。
类库组件的概念和对类库组件访问的控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚的类库单元中。Java 中通过 package 关键字加以控制,类在相同包下还是在不同包下,会影响访问修饰符。
包内包含一组类,它们被组织在一个单独的命名空间(namespace)下。import
导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。
一个 Java 源代码文件称为一个*编译单元(compilation unit)*每个编译单元的文件名后缀必须是 .java。在编译单元中可以有一个 public 类,它的类名必须与文件名相同。每个编译单元中只能有一个 public 类,否则编译器不接受。
在编译少量的 .java 文件后,会得到大量的 .class 文件。在 Java 中,可运行程序是一组 .class 文件,它们可以打包压缩成一个 Java 文档文件
如果你使用了 package 语句,它必须是文件中除了注释之外的第一行代码。
package hiding;
意味着这个编译单元是一个名为 hiding 类库的一部分。任何人想要使用该名称,必须指明完整的类名或者使用 import 关键字导入 hiding 。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同)
package 和 import 这两个关键字将单一的全局命名空间分隔开,从而避免名称冲突。
把 package 名称分解成你机器上的一个目录,当 Java 解释器必须要加载一个 .class 文件时,它能定位到 .class 文件所在的位置。首先,它找出环境变量 CLASSPATH(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。CLASSPATH 包含一个或多个目录,用作查找 .class 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名。然后这个路径与 CLASSPATH 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 .class 文件
CLASSPATH 可以包含多个不同的搜索路径。
但是在使用 JAR 文件时,有点不一样。你必须在类路径写清楚 JAR 文件的实际名称,不能仅仅是 JAR 文件所在的目录。因此,对于一个名为 grape.jar 的 JAR 文件,类路径应包括:
CLASSPATH=.;D\JAVA\LIB;C:\flavors\grape.jar
com.mindviewinc.simple.Vector
com.mindviewinc.simple.List
当编译器遇到导入 simple 库的 import 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 com/mindviewinc/simple,然后从已编译的文件中找出名称相符者(对 Vector 而言是 Vector.class,对 List 而言是 List.class)。注意,这两个类和其中要访问的方法都必须是 public 修饰的。
只要不在同一个程序中使用有冲突的名字(若使用了有冲突的名字,必须明确指明全名)
java.util.Vector v = new java.util.Vector();
调试是一个很常见的用途,调试功能在开发过程中是开启的,在发布的产品中是禁用的。可以通过改变导入的 package 来实现这一目的,修改的方法是将程序中的代码从调试版改为发布版。这个技术可用于任何种类的条件代码。
注意,编译过的代码通常位于与源代码的不同目录中。这是很多工程的标准,而且集成开发环境(IDE)通常会自动为我们做这些。必须保证 JVM 通过 CLASSPATH 能找到编译后的代码。
默认访问权限没有关键字,通常被称为*包访问权限(package access)*当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 private 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。
使用 private,你可以自由地修改那个被修饰的成员,无需担心会影响同一包下的其他类。
使用 private 是非常重要的,尤其是在多线程环境中
任何可以肯定只是该类的"助手"方法,都可以声明为 private,以确保不会在包中的其他地方误用它,也防止了你会去改变或删除它。将方法声明为 private 确保了你拥有这种选择权。
protected: 继承访问权限: 关键字 protected 处理的是继承的概念,通过继承可以利用一个现有的类——我们称之为基类,然后添加新成员到现有类中而不必碰现有类。我们还可以改变类的现有成员的行为。基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就需要使用 protected
访问控制通常被称为隐藏实现(implementation hiding)。将数据和方法包装进类中并把具体实现隐藏被称作是封装(encapsulation)。其结果就是一个同时带有特征和行为的数据类型。
出于两个重要的原因,访问控制在数据类型内部划定了边界:
【1】确立客户端程序员可以使用和不能使用的边界
【2】将接口与实现分离。如果在一组程序中使用接口,而客户端程序员只能向 public 接口发送消息的话,那么就可以自由地修改任何不是 public 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码
将接口展现给类的使用者实际上是类浏览器的任务,类浏览器会展示所有可用的类,并告诉你如何使用它们(比如说哪些成员可用)。在 Java 中,JDK 文档起到了类浏览器的作用。
访问权限修饰符可以用于确定类库中的哪些类对于类库的使用者是可用的
每个编译单元(即每个文件)中只能有一个 public 类
public 类的名称必须与含有该编译单元的文件名相同,包括大小写。
编译单元内没有 public 类也是可能的。这时可以随意命名文件(尽管随意命名会让代码的阅读者和维护者感到困惑)。
类既不能是 private 的(这样除了该类自身,任何类都不能访问它),也不能是 protected 的。所以对于类的访问权限只有两种选择:包访问权限或者 public
如果你不显式地创建构造器,编译器会自动为你创建一个无参构造器(没有参数的构造器)。如果我们编写了无参构造器,那么编译器就不会自动创建构造器了
用 C 语言开发项目,当代码量达到 5 万行和 10 万行时就会出现问题,因为 C 语言只有单一的命名空间,名称开始冲突造成额外的管理开销。在 Java 中,关键字 package,包命名模式和关键字 import 给了你对于名称的完全控制权,因此可以轻易地避免名称冲突的问题。
控制成员访问权限有两个原因:第一个原因是使用户不要接触他们不该接触的部分。第二个也是最重要的原因是为了让类库设计者更改类内部的工作方式,而不用担心会影响到客户端程序员。访问权限控制确保客户端程序员不会依赖某个类的底层实现的任何部分。
类的 public 接口是用户真正看到的,所以在分析和设计阶段决定这部分接口是最重要的部分
访问权限控制关注的是类库创建者和外部使用者之间的关系,一种交流方式。