7.JAVA编程思想笔记隐藏实施过程
Posted badman250
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7.JAVA编程思想笔记隐藏实施过程相关的知识,希望对你有一定的参考价值。
欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040237
“进行面向对象的设计时,一项基本的考虑是:如何将发生变化的东西与保持不变的东西分隔开。”
Java 推出了“访问指示符”的概念,允许库创建者声明哪些东西是客户程序员可以使用的,哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间,分别包括:public,“友好的”(无关键字),protected以及private。根据前一段的描述,大家或许已总结出作为一名库设计者,应将所有东西都尽可能保持为“private”(私有),并只展示出那些想让客户程序员使用的方法。这种思路是完全正确的,尽管它有点儿违背那些用其他语言(特别是 C)编程的人的直觉,那些人习惯于在没有任何限制的情况下访问所有东西。
组件库以及控制谁能访问那个库的组件的概念现在仍不是完整的。仍存在这样一个问题:如何将组件绑定到单独一个统一的库单元里。这是通过Java 的package(打包)关键字来实现的,而且访问指示符要受到类在相同的包还是在不同的包里的影响。
库组件如何置入包里? 这样才能理解访问指示符的完整含义。
1 包:库单元
用import 关键字导入一个完整的库时,就会获得“包”(Package)。例如:
import java.util.*;
它的作用是导入完整的实用工具(Utility)库,该库属于标准Java 开发工具包的一部分。
由于Vector 位于java.util 里,所以现在要么指定完整名称“java.util.Vector”(可省略 import 语句),要么简单地指定一个“Vector”(因为import是默认的)。 若想导入单独一个类,可在import语句里指定那个类的名字: importjava.util.Vector; 现在,可以自由地使用Vector。然而,java.util 中的其他任何类仍是不可使用的。
之所以要进行这样的导入,是为了提供一种特殊的机制,以便管理“命名空间”(NameSpace)。我们所有类成员的名字相互间都会隔离起来。位于类A 内的一个方法f()不会与位于类B 内的、拥有相同“签名”(自变量列表)的f()发生冲突。但类名会不会冲突呢?假设创建一个stack 类,将它安装到已有一个stack
类(由其他人编写)的机器上,这时会出现什么情况呢?对于因特网中的 Java 应用,这种情况会在用户毫不知晓的时候发生,因为类会在运行一个Java 程序的时候自动下载。
正是由于存在名字潜在的冲突,所以特别有必要对 Java 中的命名空间进行完整的控制,而且需要创建一个完全独一无二的名字,无论因特网存在什么样的限制。
若计划创建一个“对因特网友好”或者说“适合在因特网使用”的程序,必须考虑如何防止类名的重复。
为Java 创建一个源码文件的时候,它通常叫作一个“编辑单元”(有时也叫作“翻译单元”)。每个编译单元都必须有一个以.java结尾的名字。而且在编译单元的内部,可以有一个公共(public)类,它必须拥有与文件相同的名字(包括大小写形式,但排除.java文件扩展名)。如果不这样做,编译器就会报告出错。
每个编译单元内都只能有一个public 类(同样地,否则编译器会报告出错)。那个编译单元剩下的类(如果有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主public类的“支撑”类组成。
编译一个.java 文件时,我们会获得一个名字完全相同的输出文件;但对于.java 文件中的每个类,它们都有一个.class 扩展名。因此,我们最终从少量的.java 文件里有可能获得数量众多的.class 文件。
如以前用一种汇编语言写过程序,那么可能已习惯编译器先分割出一种过渡形式(通常是一个.obj 文件),再用一个链接器将其与其他东西封装到一起(生成一个可执行文件),或者与一个库封装到一起(生成一个库)。但那并不是Java 的工作方式。一个有效的程序就是一系列.class 文件,它们可以封装和压缩到一个 JAR文件里
“库”也由一系列类文件构成。每个文件都有一个 public类(并没强迫使用一个 public 类,但这种情况最很典型的),所以每个文件都有一个组件。如果想将所有这些组件(它们在各自独立的.java 和.class文件里)都归纳到一起,那么package 关键字就可以发挥作用)。
若在一个文件的开头使用下述代码:
package mypackage;
那么package语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为mypackage 的一个库的一部分。或者换句话说,它表明这个编译单元内的public类名位于mypackage这个名字的下面。如果其他人想使用这个名字,要么指出完整的名字,要么与mypackage联合使用import 关键字(使用前面给出的选项)。注意根据Java 包(封装)的约定,名字内的所有字母都应小写,甚至那些中间单词亦要如此。
作为一名库设计者,一定要记住package和import关键字允许我们做的事情就是分割单个全局命名空间,保证我们不会遇到名字的冲突——无论有多少人使用因特网,也无论多少人用Java 编写自己的类。
1.1 创建独一无二的包名
由于一个包永远不会真的“封装”到单独一个文件里面,它可由多个.class 文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使用的所有.class文件都置入单个目录里。这正是 Java 所采取的方法。
同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。
但根据约定,编译器强迫package名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由InterNIC保证,它控制着域名的分配),所以假如按这一约定行事,package 的名称就肯定不会重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相同的路径名编写Java 代码,否则名字的冲突是永远不会出现的。当然,如果你没有自己的域名,那么必须创造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自己的Java代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。
这个技巧的另一部分是将package 名解析成自己机器上的一个目录。这样一来,Java 程序运行并需要装载.class 文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一个static 成员时),它就可以找到.class 文件驻留的那个目录。
Java 解释器的工作程序如下:首先,它找到环境变量CLASSPATH(将Java 或者具有Java 解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH包含了一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从CLASSPATH 根开始的一个路径名(所以package foo.bar.baz 会变
成foo\\bar\\baz或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为CLASSPATH内的各个条目(入口)。以后搜索.class文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与 Java 解释器驻留的地方有关。
自Java 1.2 以来,整个包名都是小写的. CLASSPATH 里能包含大量备用的搜索路径.
1.1.1 自动编译
为导入的类首次创建一个对象时(或者访问一个类的static 成员时),编译器会在适当的目录里寻找同名的.class 文件(所以如果创建类 X的一个对象,就应该是 X.class)。若只发现X.class,它就是必须使用的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如果X.java 比X.class 新,就会自动编译X.java,生成一个最新的X.class。
对于一个特定的类,或在与它同名的.java 文件中没有找到它,就会对那个类采取上述的处理。
1.1.2 冲突
若通过*导入了两个库,而且它们包括相同的名字.例如,假定一个程序使用了下述
导入语句:
import com.bruceeckel.util.*;
import java.util.*;
由于java.util.*也包含了一个Vector 类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么就不会产生任何问题——这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能可能永远也不会发生的冲突。
如现在试着生成一个Vector。如下所示:
Vector v = new Vector();
它引用的到底是哪个Vector类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个错误,强迫我们进行明确的说明。例如,假设我想使用标准的 Java Vector,那么必须象下面这样编程:
java.util.Vector v = newjava.util.Vector();
由于它(与CLASSPATH 一起)完整指定了那个Vector的位置,所以不再需要import java.util.*语句,除非还想使用来自java.util 的其他东西。
蛤蟆对JAVA的这块设计不得不点个赞,相当NICE.
1.2 自定义工具库
创建自己的工具库,以便减少或者完全消除重复的代码。
可为System.out.println()创建一个别名,减少重复键入的代码量。
package com.toad;
publicclass P {
publicstaticvoid rint(Object obj) {
System.out.print(obj);
}
publicstaticvoid rint(String s) {
System.out.print(s);
}
publicstaticvoid rint(char[]s) {
System.out.print(s);
}
publicstaticvoid rint(charc) {
System.out.print(c);
}
publicstaticvoid rint(inti) {
System.out.print(i);
}
publicstaticvoid rint(longl) {
System.out.print(l);
}
publicstaticvoid rint(floatf) {
System.out.print(f);
}
publicstaticvoid rint(doubled) {
System.out.print(d);
}
publicstaticvoid rint(booleanb) {
System.out.print(b);
}
publicstaticvoid rintln() {
System.out.println();
}
publicstaticvoid rintln(Object obj) {
System.out.println(obj);
}
publicstaticvoid rintln(String s) {
System.out.println(s);
}
publicstaticvoid rintln(char[]s) {
System.out.println(s);
}
publicstaticvoid rintln(charc) {
System.out.println(c);
}
publicstaticvoid rintln(inti) {
System.out.println(i);
}
publicstaticvoid rintln(longl) {
System.out.println(l);
}
publicstaticvoid rintln(floatf) {
System.out.println(f);
}
publicstaticvoid rintln(doubled) {
System.out.println(d);
}
publicstaticvoid rintln(booleanb) {
System.out.println(b);
}
} // /:~
不同的数据类型现在都可以在一个新行输出(P.rintln()),或者不在一个新行输出(P.rint())。
这个文件所在的目录必须从某个CLASSPATH位置开始。编译完毕后,利用一个import 语句,即可在自己系统的任何地方使用P.class文件。
测试这个文件
import com.toad.*;
publicclass testtool {
publicstaticvoid main(String[] args) {
P.rintln("Available from now on!");
}
} ///:~
目录如下图:
输出如下:
Availablefrom now on!
后续只要做出了一个有用的新工具,就可将其加入toad 目录即可。
不过请务必保证对于类路径的每个地方,每个名字都仅存在一个类。
1.3 利用导入改变行为
Java 已取消的一种特性是C 的“条件编译”,它允许我们改变参数,获得不同的行为,同时不改变其他任何代码。Java 之所以抛弃了这一特性,可能是由于该特性经常在 C里用于解决跨平台问题:代码的不同部分根据具体的平台进行编译,否则不能在特定的平台上运行。由于 Java 的设计思想是成为一种自动跨平台的语言,所以这种特性是没有必要的。
然而,条件编译还有另一些非常有价值的用途。一种很常见的用途就是调试代码。调试特性可在开发过程中使用,但在发行的产品中却无此功能。Alen Holub(www.holub.com)提出了利用包(package)来模仿条件编译的概念。根据这一概念,它创建了C“断定机制”一个非常有用的Java版本。之所以叫作“断定机制”,是由于我们可以说“它应该为真”或者“它应该为假”。如果语句不同意你的断定,就可以发现相关的情况。这种工具在调试过程中是特别有用的。
创建类如下:
package com.toad;
publicclass Assert {
privatestaticvoid perr(String msg) {
System.err.println(msg);
}
publicfinalstaticvoid is_true(booleanexp) {
if (!exp)
perr("Assertionfailed");
}
publicfinalstaticvoid is_false(booleanexp) {
if (exp)
perr("Assertionfailed");
}
publicfinalstaticvoid is_true(booleanexp, Stringmsg) {
if (!exp)
perr("Assertionfailed: " +msg);
}
publicfinalstaticvoid is_false(booleanexp, Stringmsg) {
if (exp)
perr("Assertionfailed: " +msg);
}
} // /:~
在不同路径下创建Assert.java如下:
package com.debug;
publicclass Assert {
publicfinalstaticvoid is_true(booleanexp){}
publicfinalstaticvoid is_false(booleanexp){}
publicfinalstaticvoid
is_true(booleanexp, Stringmsg) {}
publicfinalstaticvoid
is_false(booleanexp, Stringmsg) {}
} ///:~
使用示例如下:
importcom.debug.*;
//import com.toad.*;
publicclass TestAssert {
public static void main(String[] args) {
Assert.is_true((2 + 2) == 5);
Assert.is_false((1 + 1) == 2);
Assert.is_true((2 + 2) == 5, "2 + 2 ==5");
Assert.is_false((1 + 1) == 2, "1 +1 !=2");
}
}///:~
通过改变导入的package,我们可将自己的代码从调试版本变成最终的发行版本。这种技术可应用于任何种类的条件代码。
1.4 包的停用
每次创建一个包后,都在为包取名时间接地指定了一个目录结构。这个包必须存在(驻留)于由它的名字规定的目录内。而且这个目录必须能从CLASSPATH 开始搜索并发现。最开始的时候,package关键字的运用可能会令人迷惑,因为除非坚持遵守根据目录路径指定包名的规则,否则就会在运行期获得大量莫名其妙的消息,指出找不到一个特定的类——即使那个类明明就在相同的目录中。请试着将package 语句作为注释标记出去。如果这样做行得通,就可知道问题到底出在哪儿。
2 Java 访问指示符
针对类内每个成员的每个定义,Java 访问指示符 poublic,protected 以及private都置于它们的最前面——无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。这与C++存在着显著不同。在C++中,访问指示符控制着它后面的所有定义,直到又一个访问指示符加入为止。
2.1 “友好的”
如果根本不指定访问指示符,默认的访问没有关键字,但它通常称为“友好”(Friendly)访问。这意味着当前包内的其他所有类都能访问“友好的”成员,
但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。由于一个编译单元(一个文件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元素拥有“包访问”权限。
友好访问允许我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后,便“拥有”了那个包内的代码。只有我们已经拥有的代码才能友好地访问自己拥有的其他代码。我们可认为友好访问使类在一个包内的组合显得有意义,或者说前者是后者的原因。在许多语言中,我们在文件内组织定义的方式往往显得有些牵强。但在Java 中,却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类,不想让它们访问当前包内定义的类。
对于任何关系,一个非常重要的问题是“谁能访问我们的‘私有’或private代码”。类控制着哪些代码能够访问自己的成员。没有任何秘诀可以“闯入”。另一个包内推荐可以声明一个新类,然后说:“嗨,我是Bob的朋友!”,并指望看到Bob的“protected”(受到保护的)、友好的以及“private”(私有)的成员。为获得对一个访问权限,唯一的方法就是:
(1) 使成员成为“public”(公共的)。这样所有人从任何地方都可以访问它。
(2) 变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就可以访问成员。
(3) 正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个 protected 成员,也可以访问一个 public成员(但不可访问 private成员)。只有在两个类位于相同的包内时,它才可以访问友好成员。但现在不必关心这方面的问题。
(4) 提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。
2.2 public:接口访问
使用public关键字时,它意味着紧随在public 后面的成员声明适用于所有人,特别是适用于使用库的客户程序员。
创建类如下:
publicclass Dinner {
public Dinner() {
System.out.println("Dinner constructor");
}
publicstaticvoid main(String[] args) {
Cookie x =new Cookie();
//!x.foo(); // Can't access
}
} ///:~
创建类如下:
publicclass Cookie {
public Cookie() {
System.out.println("Cookieconstructor");
}
void foo() { System.out.println("foo"); }
} ///:~
创建一个Cookie对象,因为它的构建器是public的,而且类也是public的。然而,foo()成员不可在 Dinner.java 内访问,因为foo()只有在包内才是“友好”的。
2.2.1 默认包
可能会惊讶地发现下面这些代码得以顺利编译。
classCake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
}///:~
在位于相同目录的第二个文件里:
//:Pie.java
//The other class
classPie {
void f() {System.out.println("Pie.f()"); }
}///:~
Cake 能创建一个 Pie对象,并能调用它的f()方法!通常的想法会认为Pie和f()是“友好的”,所以不适用于Cake。它们确实是友好的——这部分结论非常正确。但它们之所以仍能在Cake.java 中使用,是由于它们位于相同的目录中,而且没有明确的包名。Java 把象这样
的文件看作那个目录“默认包”的一部分,所以它们对于目录内的其他文件来说是“友好”的。
2.3 private
private关键字意味着除非那个特定的类,而且从那个类的方法里,否则没有人能访问那个成员。同一个包内的其他成员不能访问 private成员,这使其显得似乎将类与我们自己都隔离起来。另一方面,也不能由几个合作的人创建一个包。所以private 允许我们自由地改变那个成员,同时毋需关心它是否会影响同一个包内的另一个类。默认的“友好”包访问通常已经是一种适当的隐藏方法;请记住,对于包的用户来说,是不能访问一个“友好”成员的。这种效果往往能令人满意,因为默认访问是我们通常采用的方法。对于希望变成public(公共)的成员,我们通常明确地指出,令其可由客户程序员自由调用。而且作为一个结果,最开始的时候通常会认为自己不必频繁使用private关键字,因为完全可以在不用它的前提下发布自己的代码(这与C++是个鲜明的对比)。private有非常重要的用途,特别是在涉及多线程处理的时候。
下面是应用了private 的一个例子:
package com.main;
class Sundae {
private Sundae() {System.out.println("privateSundate");}
static Sundae makeASundae() {
returnnew Sundae();
}
}
publicclass IceCream {
publicstaticvoid main(String[] args) {
//!Sundaex = new Sundae();
Sundae x =Sundae.makeASundae();
}
} ///:~
这个例子向我们证明了使用private的方便:有时可能想控制对象的创建方式,并防止有人直接访问一个特定的构建器(或者所有构建器)。在上面的例子中,我们不可通过它的构建器创建一个Sundae 对象;相反,必须调用makeASundae()方法来实现。此时还会产生另一个影响:由于默认构建器是唯一获得定义的,而且它的属性是 private,所以可防止对这个类的继承。
若确定一个类只有一个“助手”方法,那么对于任何方法来说,都可以把它们设为private,从而保证自己不会误在包内其他地方使用它,防止自己更改或删除方法。将一个方法的属性设为private后,可保证自己一直保持这一选项(然而,若一个句柄被设为 private,并不表明其他对象不能拥有指向同一个对象的public句柄。
2.4 protected
protected 关键字为我们引入了一种名为“继承”的概念,它以现有的类为基础,并在其中加入新的成员,同时不会对现有的类产生影响——我们将这种现有的类称为“基础类”或者“基本类”(Base Class)。亦可改变那个类现有成员的行为。对于 以上是关于7.JAVA编程思想笔记隐藏实施过程的主要内容,如果未能解决你的问题,请参考以下文章