Thinking in Java ---ch05笔记

ch05 Hiding the Implementation

5.1 包:库单元

我们用import关键字导入一个完整的库时,就会获得Package)。

 

编译一个.java文件时,我们会获得一个名字完全相同的输出文件;但对于.java文件中的每个类,它们都有一个.class扩展名。因此,我们最终从少量的.java文件里有可能获得数量众多的.class文件。

 

一个有效的程序就是一系列.class文件,它们可以封装和压缩到一个JAR文件里(使用Java 1.1提供的jar工具)。Java解释器负责对这些文件的寻找、装载和解释(注释)。

 

作为一名库设计者,一定要记住packageimport关键字允许我们做的事情就是分割单个全局命名空间,保证我们不会遇到名字的冲突——无论有多少人使用因特网,也无论多少人用Java编写自己的类。

 

5.1.1创建独一无二的包名

Java解释器的工作程序如下:

1)首先,它找到环境变量CLASSPATH(将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。

2CLASSPATH包含了一个或多个目录,它们作为一种特殊的使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从CLASSPATH根开始的一个路径名(所以package foo.bar.baz会变成foo/bar/baz或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。

3)随后将它们连接到一起,成为CLASSPATH内的各个条目(入口)。

4)以后搜索.class文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。

 

一个例子:(记得开始学习java的时候,classpath 总是设置不好,现在想起来,原本就没有真正理解classpath的意义)

由于决定创建一个名为util的库,我可以进一步地分割它,所以最后得到的包名如下:
package com.bruceeckel.util;
现在,可将这个包名作为下述两个文件的命名空间使用:

//: Vector.java

// Creating a package

package com.bruceeckel.util;

 

public class Vector {

  public Vector() {

    System.out.println(

      "com.bruceeckel.util.Vector");

  }

} ///:~


创建自己的包时,要求package语句必须是文件中的第一个非注释代码。第二个文件表面看起来是类似的:

//: List.java

// Creating a package

package com.bruceeckel.util;

 

public class List {

  public List() {

    System.out.println(

      "com.bruceeckel.util.List");

  }

} ///:~


这两个文件都置于我自己系统的一个子目录中:
C:/DOC/JavaT/com/bruceeckel/util
若通过它往回走,就会发现包名com.bruceeckel.util,但路径的第一部分又是什么呢?这是由CLASSPATH环境变量决定的。在我的机器上,它是:
CLASSPATH=.;D:/JAVA/LIB;C:/DOC/JavaT
可以看出,CLASSPATH里能包含大量备用的搜索路径。然而,使用JAR文件时要注意一个问题:必须将JAR文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape.jarJAR文件来说,我们的类路径需要包括:
CLASSPATH=.;D:/JAVA/LIB;C:/flavors/grape.jar
正确设置好类路径后,可将下面这个文件置于任何目录里(若在执行该程序时遇到麻烦,请参见第3章的3.1.2小节赋值):

//: LibTest.java

// Uses the library

package c05;

import com.bruceeckel.util.*;

 

public class LibTest {

  public static void main(String[] args) {

    Vector v = new Vector();

    List l = new List();

  }

} ///:~


编译器遇到import语句后,它会搜索由CLASSPATH指定的目录,查找子目录com/bruceeckel/util,然后查找名称适当的已编译文件(对于VectorVector.class,对于List则是List.class)。注意VectorList内无论类还是需要的方法都必须设为public

1. 自动编译
为导入的类首次创建一个对象时(或者访问一个类的static成员时),编译器会在适当的目录里寻找同名的.class文件(所以如果创建类X的一个对象,就应该是X.class)。若只发现X.class,它就是必须使用的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如果X.javaX.class新,就会自动编译X.java,生成一个最新的X.class
对于一个特定的类,或在与它同名的.java文件中没有找到它,就会对那个类采取上述的处理。

2. 冲突
若通过*导入了两个库,而且它们包括相同的名字,这时会出现什么情况呢?例如,假定一个程序使用了下述导入语句:
import com.bruceeckel.util.*;
import java.util.*;
由于java.util.*也包含了一个Vector类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么就不会产生任何问题——这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能可能永远也不会发生的冲突。
如现在试着生成一个Vector,就肯定会发生冲突。如下所示:
Vector v = new Vector();
它引用的到底是哪个Vector类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个错误,强迫我们进行明确的说明。例如,假设我想使用标准的Java Vector,那么必须象下面这样编程:
java.util.Vector v = new java.util.Vector();
由于它(与CLASSPATH一起)完整指定了那个Vector的位置,所以不再需要import java.util.*语句,除非还想使用来自java.util的其他东西。

 

5.2 Java访问指示符
针对类内每个成员的每个定义,Java访问指示符poublicprotected以及private都置于它们的最前面——无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。

5.2.1 “友好的
如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键字,但它通常称为友好Friendly)访问。

默认的访问没有关键字,但它通常称为友好Friendly)访问。这意味着当前包内的其他所有类都能访问友好的成员,但对包外的所有类来说,这些成员却是私有Private)的,外界不得访问。由于一个编译单元(一个文件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动友好的。因此,我们也说友好元素拥有包访问权限。

 

Friendly: almost the same as protected(not the same, see the protected)(包权限,不能被子类访问)

Public: every one can call it

Private: only can be called inside the class

Protected: can be called in the same package(包权限,可以被子类访问)

 

5.2.3 private:不能接触!
private关键字意味着除非那个特定的类,而且从那个类的方法里,否则没有人能访问那个成员。同一个包内的其他成员不能访问private成员,这使其显得似乎将类与我们自己都隔离起来。另一方面,也不能由几个合作的人创建一个包。所以private允许我们自由地改变那个成员,同时毋需关心它是否会影响同一个包内的另一个类。这种效果往往能令人满意,因为默认访问是我们通常采用的方法。对于希望变成public(公共)的成员,我们通常明确地指出,令其可由客户程序员自由调用。而且作为一个结果,最开始的时候通常会认为自己不必频繁使用private关键字,因为完全可以在不用它的前提下发布自己的代码(这与C++是个鲜明的对比)。然而,随着学习的深入,大家就会发现private仍然有非常重要的用途,特别是在涉及多线程处理的时候(详情见第14章)。
下面是应用了private的一个例子:

//: IceCream.java

// Demonstrates "private" keyword

 

class Sundae {

  private Sundae() {}

  static Sundae makeASundae() {

    return new Sundae();

  }

}

 

public class IceCream {

  public static void main(String[] args) {

    //! Sundae x = new Sundae();

    Sundae x = Sundae.makeASundae();

  }

} ///:~


这个例子向我们证明了使用private的方便:有时可能想控制对象的创建方式,并防止有人直接访问一个特定的构建器(或者所有构建器)。
在上面的例子中,我们不可通过它的构建器创建一个Sundae对象;相反,必须调用makeASundae()方法来实现(注释)。

:此时还会产生另一个影响:由于默认构建器是唯一获得定义的,而且它的属性是private,所以可防止对这个类的继承(这是第6章要重点讲述的主题)。

 

5.2.4 protected友好的一种对于继承来说,protected应该比frienly的权限范围带一点,从下面那个例子可以看出
protected关键字为我们引入了一种名为继承的概念,它以现有的类为基础,并在其中加入新的成员,同时不会对现有的类产生影响——我们将这种现有的类称为基础类或者基本类Base Class)。亦可改变那个类现有成员的行为。对于从一个现有类的继承,我们说自己的新类扩展extends)了那个现有的类。如下所示:
class Foo extends Bar {
类定义剩余的部分看起来是完全相同的。
若新建一个包,并从另一个包内的某个类里继承,则唯一能够访问的成员就是原来那个包的public成员。当然,如果在相同的包里进行继承,那么继承获得的包能够访问所有友好的成员。有些时候,基础类的创建者喜欢提供一个特殊的成员,并允许访问衍生类。这正是protected的工作。

 

 

若往回引用5.2.2小节“public:接口访问的那个Cookie.java文件,则下面这个类就不能访问友好的成员:

//: Cookie.java
  
  
// Creates a library
  
  
package c05.dessert;
  
  

  
  
   
    
  
  
public class Cookie {
  
  
  public Cookie() { 
  
  
   System.out.println("Cookie constructor"); 
  
  
  }
  
  
  void foo() { System.out.println("foo"); }
  
  
} ///:~
  
  

foofriendly

============================

//: ChocolateChip.java

// Can't access friendly member

// in another class

import c05.dessert.*;

 

public class ChocolateChip extends Cookie {

  public ChocolateChip() {

   System.out.println(

     "ChocolateChip constructor");

  }

  public static void main(String[] args) {

    ChocolateChip x = new ChocolateChip();

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

  }

} ///:~


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

public class Cookie {

  public Cookie() {

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

  }

  protected void foo() {

    System.out.println("foo");

  }

}

foo()is protected instead of friendly, then it can be accessed by its child class

---------


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值