欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040237

“进行面向对象的设计时,一项主要的考虑是:怎样将发生变化的东西与保持不变的东西分隔开。”

      

Java 推出了“訪问指示符”的概念,同意库创建者声明哪些东西是客户程序猿能够使用的,哪些是不可使用的。这样的訪问控制的级别在“最大訪问”和“最小訪问”的范围之间,分别包含:public,“友好的”(无keyword)。protected以及private。依据前一段的描写叙述。大家也许已总结出作为一名库设计者。应将全部东西都尽可能保持为“private”(私有),并仅仅展示出那些想让客户程序猿使用的方法。这样的思路是全然正确的,虽然它有点儿违背那些用其它语言(特别是 C)编程的人的直觉,那些人习惯于在没有不论什么限制的情况下訪问全部东西。

组件库以及控制谁能訪问那个库的组件的概念如今仍不是完整的。

仍存在这样一个问题:怎样将组件绑定到单独一个统一的库单元里。这是通过Java 的package(打包)keyword来实现的。并且訪问指示符要受到类在同样的包还是在不同的包里的影响。

库组件怎样置入包里?

这样才干理解訪问指示符的完整含义。

 

1      包:库单元

用import keyword导入一个完整的库时,就会获得“包”(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 keyword就能够发挥作用)。

若在一个文件的开头使用下述代码:

package mypackage;

那么package语句必须作为文件的第一个非凝视语句出现。该语句的作用是指出这个编译单元属于名为mypackage 的一个库的一部分。

或者换句话说。它表明这个编译单元内的public类名位于mypackage这个名字的以下。

假设其它人想使用这个名字,要么指出完整的名字,要么与mypackage联合使用import keyword(使用前面给出的选项)。注意依据Java 包(封装)的约定,名字内的全部字母都应小写。甚至那些中间单词亦要如此。

作为一名库设计者,一定要记住package和importkeyword同意我们做的事情就是切割单个全局命名空间,保证我们不会遇到名字的冲突——不管有多少人使用因特网,也不管多少人用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 開始搜索并发现。

最開始的时候。packagekeyword的运用可能会令人迷惑。由于除非坚持遵守依据文件夹路径指定包名的规则,否则就会在执行期获得大量莫名其妙的消息,指出找不到一个特定的类——即使那个类明明就在同样的文件夹中。请试着将package 语句作为凝视标记出去。假设这样做行得通,就可知道问题究竟出在哪儿。

 

2      Java 訪问指示符

针对类内每一个成员的每一个定义,Java 訪问指示符 poublic。protected 以及private都置于它们的最前面——不管它们是一个数据成员,还是一个方法。每一个訪问指示符都仅仅控制着对那个特定定义的訪问。这与C++存在着显著不同。在C++中。訪问指示符控制着它后面的全部定义,直到又一个訪问指示符增加为止。

 

2.1      “友好的”

假设根本不指定訪问指示符,默认的訪问没有keyword,但它通常称为“友好”(Friendly)訪问。这意味着当前包内的其它全部类都能訪问“友好的”成员,

但对包外的全部类来说。这些成员却是“私有”(Private)的。外界不得訪问。

因为一个编译单元(一个文件)仅仅能从属于单个包,所以单个编译单元内的全部类相互间都是自己主动“友好”的。因此。我们也说友好元素拥有“包訪问”权限。

友好訪问同意我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后,便“拥有”了那个包内的代码。仅仅有我们已经拥有的代码才干友好地訪问自己拥有的其它代码。我们可觉得友好訪问使类在一个包内的组合显得有意义,或者说前者是后者的原因。在很多语言中。我们在文件内组织定义的方式往往显得有些牵强。但在Java 中。却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类。不想让它们訪问当前包内定义的类。

对于不论什么关系,一个很重要的问题是“谁能訪问我们的‘私有’或private代码”。类控制着哪些代码能够訪问自己的成员。没有不论什么秘诀能够“闯入”。

还有一个包内推荐能够声明一个新类,然后说:“嗨,我是Bob的朋友!

”,并指望看到Bob的“protected”(受到保护的)、友好的以及“private”(私有)的成员。为获得对一个訪问权限,唯一的方法就是:

(1) 使成员成为“public”(公共的)。这样全部人从不论什么地方都能够訪问它。

(2) 变成一个“友好”成员,方法是舍弃全部訪问指示符,并将其类置于同样的包内。

这样一来,其它类就能够訪问成员。

(3) 正如以后引入“继承”概念后大家会知道的那样。一个继承的类既能够訪问一个 protected 成员。也能够訪问一个 public成员(但不可訪问 private成员)。

仅仅有在两个类位于同样的包内时。它才干够訪问友好成员。

但如今不必关心这方面的问题。

(4) 提供“訪问器/变化器”方法(亦称为“获取/设置”方法),以便读取和改动值。

 

2.2      public:接口訪问

使用publickeyword时。它意味着紧随在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 

privatekeyword意味着除非那个特定的类。并且从那个类的方法里。否则没有人能訪问那个成员。同一个包内的其它成员不能訪问 private成员,这使其显得似乎将类与我们自己都隔离起来。还有一方面,也不能由几个合作的人创建一个包。

所以private 同意我们自由地改变那个成员,同一时候毋需关心它是否会影响同一个包内的还有一个类。

默认的“友好”包訪问通常已经是一种适当的隐藏方法;请记住。对于包的用户来说,是不能訪问一个“友好”成员的。这样的效果往往能令人惬意。由于默认訪问是我们通常採用的方法。对于希望变成public(公共)的成员,我们通常明白地指出,令其可由客户程序猿自由调用。并且作为一个结果。最開始的时候一般会觉得自己不必频繁使用privatekeyword,由于全然能够在不用它的前提下公布自己的代码(这与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 keyword为我们引入了一种名为“继承”的概念,它以现有的类为基础,并在当中增加新的成员。同一时候不会对现有的类产生影响——我们将这样的现有的类称为“基础类”或者“基本类”(Base Class)。

亦可改变那个类现有成员的行为。对于从一个现有类的继承,我们说自己的新类“扩展”(extends)了那个现有的类。

例如以下所看到的:

class Foo extends Bar {

类定义剩余的部分看起来是全然同样的。

若新建一个包。并从还有一个包内的某个类里继承,则唯一可以訪问的成员就是原来那个包的public成员。当然,假设在同样的包里进行继承。那么继承获得的包可以訪问全部“友好”的成员。有些时候,基础类的创建者喜欢提供一个特殊的成员,并同意訪问衍生类。

这正是protected的工作。

扩展之前的cookie类例如以下:

package com.main;

publicclass ChocolateChipextends Cookie {

 public ChocolateChip() {

  System.out.println(

    "ChocolateChipconstructor");

 }

 publicstaticvoid main(String[] args) {

   ChocolateChip x =new ChocolateChip();

   //!x.foo(); // Can't accessfoo

 }

} ///:~

对于继承。值得注意的一件有趣的事情是倘若方法 foo()存在于类Cookie 中。那么它也会存在于从Cookie继承的全部类中。但因为foo()在外部的包里是“友好”的,所以我们不能使用它。当然,亦可将其变成public。但这样一来,因为全部人都能自由訪问它。所以可能并不是我们所希望的局面。

若象以下这样改动类Cookie:  

public class Cookie {

 public Cookie() { 

   System.out.println("Cookie constructor");

  }

 protected void foo() {

   System.out.println("foo"); 

  }

}

    那么仍然能在包dessert里“友好”地訪问 foo(),但从Cookie 继承的其它东西亦可自由地訪问它。然而。它并不是公共的(public)。

 

2.5      接口与实现

我们通常觉得訪问控制是“隐藏实施细节”的一种方式。

将数据和方法封装到类内后,可生成一种数据类型。它具有自己的特征与行为。但因为双方面重要的原因。訪问为那个数据类型加上了自己的边界。

第一个原因是规定客户程序猿哪些可以使用,哪些不能。我们可在结构里构建自己的内部机制,不用操心客户程序猿将其当作接口的一部分,从而自由地使用或者“滥用”。

这个原因直接导致了第二个原因:我们须要将接口同实施细节分离开。若结构在一系列程序中使用,但用户除了将消息发给public接口之外,不能做其它不论什么事情,我们就能够改变不属于 public 的全部东西(如“友好的”、protected 以及private),同一时候不要求用户对他们的代码作不论什么改动。

我们如今是在一个面向对象的编程环境中。当中的一个类(class)实际是指“一类对象”。

从属于这个类的全部对象都共享这些特征与行为。

“类”是对属于这一类的全部对象的外观及行为进行的一种描写叙述。

在一些早期 OOP语言中。如 Simula-67,keywordclass 的作用是描写叙述一种新的数据类型。相同的keyword在大多数面向对象的编程语言里都得到了应用。

Java中。类是最主要的OOP概念。为清楚起见,可考虑用特殊的样式创建一个类:将 public成员置于最开头。后面尾随protected、友好以及private成员。这样做的优点是类的使用者可从上向下依次阅读,并首先看到对自己来说最重要的内容(即public成员。由于它们可从文件的外部訪问)。并在遇到非公共成员后停止阅读。后者已经属于内部实施细节的一部分了。然而,利用由javadoc 提供支持的凝视文档,代码的可读性问题已在非常大程度上得到了解决。

 

由于接口和实施细节仍然混合在一起,所以仅仅是部分easy阅读。也就是说,仍然可以看到源代码——实施的细节,由于它们须要保存在类里面。向一个类的消费者显示出接口实际是“类浏览器”的工作。这样的工具能查找所有可用的类,总结出可对它们採取的所有操作(比方可以使用哪些成员等)。并用一种清爽悦目的形式显示出来。

 

 

2.6      类訪问

在Java 中,可用訪问指示符推断出一个库内的哪些类可由那个库的用户使用。

若想一个类能由客户程序猿调用,可在类主体的起始花括号前面某处放置一个 publickeyword。它控制着客户程序猿是否可以创建属于这个类的一个对象。

为控制一个类的訪问。指示符必须在keywordclass 之前出现。所以我们可以使用:

public class Widget {

也就是说。假若我们的库名是mylib。那么全部客户程序猿都能訪问Widget——通过下述语句:

import mylib.Widget;

或者

import mylib.*;

然而,我们同一时候还要注意到一些额外的限制:

(1) 每一个编译单元(文件)都仅仅能有一个public。每一个编译单元有一个公共接口的概念是由那个公共类表达出来的。依据自己的须要。它可拥有随意多个提供支撑的“友好”类。

但若在一个编译单元里使用了多个public类,编译器就会向我们提示一条出错消息。

(2) public类的名字必须与包括了编译单元的那个文件的名字全然相符。甚至包括它的大写和小写形式。

所以对于Widget 来说,文件的名字必须是Widget.java,而不应是 widget.java或者WIDGET.java。相同地,假设出现不符,就会报告一个编译期错误。

(3) 可能(但并常见)有一个编译单元根本没有不论什么公共类。此时。可按自己的意愿随意指定文件名称。

   将public keyword从类中剔除就可以。这样便把类变成了“友好的”(类仅能在包内使用)。

注意不可将类设成private(那样会使除类之外的其它东西都不能訪问它)。也不能设成protected。

因此,我们如今对于类的訪问仅仅有两个选择:“友好的”或者public

若不愿其它不论什么人訪问那个类。可将全部构建器设为private。这样一来,在类的一个static 成员内部,除自己之外的其它全部人都无法创建属于那个类的一个对象。

示比例如以下:

package com.main;

 

class Soup {

    private Soup() {

        System.out.println("privatesoup");

    }

 

    // (1) Allow creationvia static method:

    publicstatic Soup makeSoup() {

        returnnew Soup();

    }

 

    // (2) Create astatic object and

    // return a referenceupon request.

    // (The"Singleton" pattern):

    privatestatic Soupps1 =new Soup();

 

    publicstatic Soup access() {

        returnps1;

    }

 

    publicvoid f() {

        System.out.println("soup.f()");

    }

}

 

class Sandwich {// Uses Lunch

    void f() {

        new Lunch();

    }

}

 

// Only one public class allowed perfile:

publicclass Lunch {

    void test() {

        // Can't do this!Private constructor:

        // ! Soup priv1 = newSoup();

        Souppriv2 = Soup.makeSoup();

        Sandwichf1 =new Sandwich();

        Soup.access().f();

    }

    Lunch(){

        System.out.println("Lunch");

    }

   

} // /:~

Soup 类向我们展示出如何通过将全部构建器都设为 private,从而防止直接创建一个类。

假若不明白地至少创建一个构建器。就会自己主动创建默认构建器(没有自变量)。

若自己编写默认构建器,它就不会自己主动创建。把它变成private后。就没人能为那个类创建一个对象。但别人如何使用这个类呢?上面的样例为我们揭示出了两个选择。

第一个选择,我们可创建一个static 方法,再通过它创建一个新的Soup,然后返回指向它的一个句柄。假设想在返回之前对Soup 进行一些额外的操作。或者想了解准备创建多少个 Soup 对象(可能是为了限制它们的个数),这样的方案无疑是特别实用的。

第二个选择是採用“设计方案”(Design Pattern)技术。

通常方案叫作“独子”,由于它仅同意创建一个对象。类Soup 的对象被创建成Soup 的一个 static private 成员。所以有一个并且仅仅能有一个。除非通过public 方法access(),否则根本无法訪问它。假设不针对类的訪问设置一个訪问指示符,那么它会自己主动默觉得“友好的”。

这意味着那个类的对象可由包内的其它类创建,但不能由包外创建。对于同样文件夹内的全部文件。假设没有明白地进行package声明,那么它们都默觉得那个文件夹的默认包的一部分。然而,假若那个类一个static成员的属性是public。那么客户程序猿仍然可以訪问那个static 成员——即使它们不能创建属于那个类的一个对象。

加入測试文件test.java例如以下:

package com.main;

importcom.debug.*;

// import com.toad.*;

publicclass test {

 publicstaticvoid main(String[] args) {

   Lunch l=new Lunch();

   l.test();

 }

} ///:~

最后输出例如以下:

Lunch

privatesoup

privatesoup

soup.f()

 

 

3      总结

创建一个库时。相当于建立了同那个库的用户(即“客户程序猿”)的一种关系——那些用户属于另外的程序猿,可能用我们的库自行构建一个应用程序,或者用我们的库构建一个更大的库。

假设不制订规则。客户程序猿就能够随心所欲地操作一个类的全部成员,不管我们本来愿不愿意当中的一些成员被直接操作。全部东西都在别人面前都暴露无遗。

因为C仅有一个“命名空间”,所以名字会開始互相抵触,从而造成额外的管理开销。而在Java 中,packagekeyword、包命名方案以及importkeyword为我们提供对名字的全然控制。所以命名冲突的问题能够非常轻易地得到避免。

有双方面的原因要求我们控制对成员的訪问。第一个是防止用户接触那些他们不应碰的工具

对于数据类型的内部机制,那些工具是必需的。

但它们并不属于用户接口的一部分,用户不必用它来解决自己的特定问题。所以将方法和字段变成“私有”(private)后,可极慷慨便用户。由于他们能轻易看出哪些对于自己来说是最重要的,以及哪些是自己须要忽略的。这样便简化了用户对一个类的理解。

进行訪问控制的第二个、也是最重要的一个原因是:同意库设计者改变类的内部工作机制。同一时候不必操心它会对客户程序猿产生什么影响。

最開始的时候,可用一种方法构建一个类,后来发现须要又一次构建代码,以便达到更快的速度。如接口和实施细节早已进行了明白的分隔与保护。就能够轻松地达到自己的目的,不要求用户改写他们的代码。

利用Java 中的訪问指示符,可有效控制类的创建者。那个类的用户可确切知道哪些是自己可以使用的。哪些则是可以忽略的。但更重要的一点是,它可确保没有不论什么用户能依赖一个类的基础实施机制的不论什么部分。

作为一个类的创建者,我们可自由改动基础的实施细节,这一改变不会对客户程序猿产生不论什么影响,由于他们不能訪问类的那一部分。

有能力改变基础的实施细节后,除了能在以后改进自己的设置之外,也同一时候拥有了“犯错误”的自由。不管当初计划与设计时有多么细致。仍然有可能出现一些失误。因为知道自己能相当安全地犯下这样的错误,所以能够放心大胆地进行很多其它、更自由的试验。

这对自己编程水平的提高是非常有帮助的。使整个项目终于能更快、更好地完毕。

一个类的公共接口是全部用户都能看见的,所以在进行分析与设计的时候。这是应尽量保证其准确性的最重要的一个部分。但也不必过于紧张。少许的误差仍然是同意的。若最初设计的接口存在少许问题,可考虑加入很多其它的方法。仅仅要保证不删除客户程序猿已在他们的代码里使用的东西。

 

 

posted on 2017-07-25 18:32  yutingliuyl  阅读(266)  评论(0编辑  收藏  举报