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编程思想笔记隐藏实施过程的主要内容,如果未能解决你的问题,请参考以下文章

python学习笔记9:面向对象编程,类

Java编程思想 学习笔记8

Java编程思想读书笔记_第二章

Java编程思想笔记 —— 内部类

Java编程思想重点笔记(Java开发必看)

python学习笔记(十九)面向对象编程,类