第 1 章 对象入门 
“为什么面向对象的编程会在软件开发领域造成如此震憾的影响?” 
对象抽象于现实,面向对象编程能够让人们更容易理解程序。 
1 . 1 抽象的进步 
一般程序设计需要考虑计算机的结构,而非考虑问题本身的结构。在机器模型(位于“机器空间”)与实际解决的问题模型(位于“问题空间”)之间,程序员必须建立起一种联系。这个过程要求人们付出较大的精力,而且由于它脱离了编程语言本身的范围,造成程序代码很难编写,而且要花较大的代价进行维护。
面向对象的程序设计将问题空间中的元素以及它们在方案空间的表示物称作“对象”(Object)。根据机器对象来解决问题对象,而不是根据机器空间。然而,仍有一个联系途径回到机器空间。
(1)一切皆为对象,对象具有属性和行为。
(2) 程序是调用对象方法解决问题。
1 . 2 对象的接口 
一切皆为对象,尽管各有特色,但某一系列对象的具有通用的特征和行为,属于一种“类型”(接口),而“类”是那个接口的一种特殊实现方式。接口是面向对象程序设计的基础。 
下面让我们以电灯泡为例: 
Light lt = new Light(); 
lt.on(); 
通过调用Light 对象的on方法实现开灯实际问题。
Light lt = new Light();创建灯对象,It指向这个对象 
It.on();调用这个灯对象的打开行为。
1 . 3 实现方案的隐藏 
涉足面向对象编程的人员:“类设计者”和“类使用者”,
类只向客户程序员开放有必要开放的东西(接口),其他所有细节都隐藏起来。
接口向使用者提供服务,但却隐藏服务细节。这些代码与那些隐藏起来的数据便叫作“隐藏的实现”。 
如果使用者接触服务细节,那么设计者若想修改服务细节,不知道会对使用造成什么影响。
例1、 我要装修房屋,找了一家装修公司,挑选了设计方案,如果我知道设计细节,并且换了更好一些更好的材质,而装修公司是先前的设计方案装修,造成了差价。
例2、 我要装修房屋,找了一家装修公司,挑选了设计方案,如果我不知道设计细节、装修公司可以更换一些合适的材质,让房屋更漂亮,会让我很高兴也让装修公司赢得了信誉。
若接口与实现方法早已隔离开,并分别受到保护,就可放心做到这一点,只要求用户重新链接一下即可。 
Java 采用三个显式(明确)关键字以及一个隐式(暗示)关键字来设置类边界:private < protected < friendly < public。 
1 . 4 方案的重复使用 
重复使用一个类,最简单的办法是仅直接使用那个类的对象。
也能将那个类的一个对象置入一个新类。新类可由任意数量和类型的其他对象构成。这个概念叫作组织,比如“一辆车包含了一个变速箱”。
因为编译器必须对通过继承创建的类加以限制。沿这种思路产生的设计将是非常笨拙的,会大大增加程序的复杂程度。 
1 . 5 继承:重新使用接口 
我们费尽心思做出一种数据类型后,假如不得不又新建一种类型,令其实现大致相同的功能,那会是一件非常令人灰心的事情。但若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。 
继承尽管private 成员被隐藏起来,但更重要的是,它复制了基础类的接口。这意味着衍生类具有与基础类相同的类型!
为真正理解面向对象程序设计的含义,首先必须认识到这种类型的等价关系。 
1 . 5 . 1 改善基础类 
改变基础类一个现有函数的行为。我们将其称作“改善”那个函数。为改善一个函数,只需为衍生类的函数建立一个新定义即可。我们的目标是:“尽管使用的函数接口未变,但它的新版本具有不同的表现”。  
1 . 5 . 2 等价与类似关系 
与基础类完拥有完全相同的接口。我们完全能够将衍生类的一个对象换成基础类的一个对象!存在一种“等价”关系。但在许多时候,我们必须为衍生类型加入新的接口元素。这种新类型仍可替换成基础类型,但不可在基础类里访问新函数。我们将其称作“类似”关系。需要注意的是类似关系经转换为基类后,不能访问新接口。 
1 . 6 多形对象的互换使用 
将衍生类的对象当作基础类的一个对象对待,因为它意味着我们只需编写单一的代码,令其忽略类型的特定细节,只与基础类打交道。这样一来,那些代码就可与类型信息分开。所以更易编写,也更易理解。 
 
//处理图形的一个方法
void doStuff(Shape s) { 
s.erase(); 
// ... 
s.draw(); 

Circle c = new Circle(); 
Triangle t = new Triangle(); 
Line l = new Line(); 
doStuff(c); 
doStuff(t); 
doStuff(l); 
我们将这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型)。这是避免去调查准确类型的一个好办法,知道传进来的是个shape,那么就可以调用shape的接口。
注意
1如果子类添加了新的接口将不能被使用。
2我们在重写父类方法时,不要改变其本质,例如圆的erase()其实现改为画一条线,想想上面的代码会怎样?1 . 6 . 1 动态绑定 
当Java编译器为 doStuff()编译代码时,它知道要操作类型是Shape,在运行时 Circle,Square,Line被正确的调用了。这是怎么做到的呢? 
这种情况就叫作“多形性”(Polymorphism),实现多形性的方法叫作“动态绑定”。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情,如何利用它帮助自己设计程序。 
1 . 6 . 2 抽象的基础类和接口 
当基础类只为自己的衍生类提供接口,不想其他任何人创建其对象,只用于upcasting,以使用其接口。为达到这个目的,需要把那个类变成“抽象”的,编译器不允许常见抽象类的对象。接口将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。
注意,应该严格限制类使用者的权限,只开发必要接口,这样有利于类设计者以后的维护。

解决方案 »

  1.   

    not more,but a little 
      

  2.   

    看见这些东西我就头晕,都答应别人要好好看thinking in Java了,可是看到那么多汉字就害怕
      

  3.   


    第 2 章 一切都是对象 
    2 . 1 用句柄操纵对象 
    可将这一情形想象成用遥控板(句柄)操纵电视机(对象)。
    String s;
    这里创建的只是句柄,并不是对象。若此时向s 发送一条消息,就会获得一个错误(运行期)。这是由于s 实际并未与任何东西连接(即“没有电视机”)。因此,一种更安全的做法是:创建一个句柄时,记住无论如何都进行初始化: 
    String s = "asdf"; 
    这里采用的是一种特殊类型:字串可用加引号的文字初始化。通常,必须为对象使用一种更通用的初始化类型。 
    2 . 2 所有对象都必须创建 
    String s = new String("asdf"); 
    它不仅指出“将我变成一个新字串”,也通过提供一个初始字串,指出了“如何生成这个新字串”。 
    2 . 2 . 1 保存到什么地方 
    程序运行时,数据保存到什么地方做到心中有数。特别要注意的是内存的分配。有六个地方都可以保存数据: 
    (1) 寄存器。最快的保存区域,因为它位于处理器内部。根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。 
    (2) 堆栈。 速度仅次于寄存器。创建程序时,Java 编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。对应java的基本类型和存储对象的引用。 
    (3) 堆。保存Java 对象。不必知道要从堆里分配多少存储空间,停留多长的时间,但在堆里分配存储空间时会花掉更长的时间! 
    (4) 静态存储。是指“位于固定位置”。static指出一个对象的特定元素是静态的。但 Java 对象本身永远都不会置入静态存储空间。 
    (5) 常数存储。常数值置入只读存储器,对应于final指定的变量。 
    (6) 非 RAM 存储。数据完全独立于一个程序之外,对于流式对象,对象会变成字节流,通常会发给另一台机器。而对于固定对象,对象保存在磁盘中。
    2 . 2 . 2 特殊情况:主要类型 
    有一系列类需特别对待;可将它们想象成“基本”、“主要”或者“主”(Primitive)类型,进行程序设计 时要频繁用到它们。之所以要特别对待,是由于用 new 创建对象(特别是小的、简单的变量)并不是非常有效,因为new 将对象置于“堆”里。对于这些类型,Java 采纳了与 C 和 C++相同的方法。也就是说,不是用 
    new 创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。Java 决定了每种主要类型的大小。就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是 Java 程序具有很强移植能力的原因之一。 
    主类型 大小 最小值 最大值 封装器类型 
    boolean 1 位 - - Boolean 
    char 16 位 Unicode 0 Unicode 2 的 16 次方-1 Character 
    byte 8 位 -128 +127 Byte(注释①) 
    short 16 位 -2 的 15 次方 +2 的 15 次方-1 Short(注释①) 
    int 32 位 -2 的 31 次方 +2 的 31 次方-1 Integer 
    long 64 位 -2 的 63 次方 +2 的 63 次方-1 Long 
    float 32 位 IEEE754 IEEE754 Float 
    double 64 位 IEEE754 IEEE754 Double 
    Void - - - Void(注释①) 
    数值类型全都是有符号(正负号)的,所以不必费劲寻找没有符号的类型。 
    主数据类型也拥有自己的“封装器”(wrapper)类。这意味着假如想让堆内一个非主要对象表示那个主类 
    型,就要使用对应的封装器。例如: 
    char c = 'x'; 
    Character C = new Character('c'); 
    也可以直接使用: 
    Character C = new Character('x'); 
    这样做的原因将在以后的章节里解释。 
    1. 高精度数字 
    Java 1.1 增加了两个类,用于进行高精度的计算:BigInteger 和 BigDecimal。尽管它们大致可以划分为 
    “封装器”类型,但两者都没有对应的“主类型”。 
    这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作。也就是说,能对int 或 float 做的 
    事情,对 BigInteger 和 BigDecimal 一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵 
    涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。 
    BigInteger 支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢 
    失任何信息。 
    BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的币值计算。 
    至于调用这两个类时可选用的构建器和方法,请自行参考联机帮助文档。 
    2 . 2 . 3 J a v a 的数组 
    几乎所有程序设计语言都支持数组。在C 和 C++里使用数组是非常危险的,因为那些数组只是内存块。若程序访问自己内存块以外的数组,或者在初始化之前使用内存(属于常规编程错误),会产生不可预测的后果 注释②。 
    ②:在 C++里,应尽量不要使用数组,换用标准模板库(Standard TemplateLibrary)里更安全的容器。 
    Java 的一项主要设计目标就是安全性。所以在 C 和 C++里困扰程序员的许多问题都未在 Java 里重复。一个Java 可以保证被初始化,而且不可在它的范围之外访问。由于系统自动进行范围检查,所以必然要付出一些代价:针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。但由此换回的是更高的安全性,以及更高的工作效率。为此付出少许代价是值得的。创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦 Java 看到 null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为 null 的一个句柄,就会在运行期报告问题。因此,典型的数组错误在Java 里就得到了避免。也可以创建主类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。 数组问题将在以后的章节里详细讨论。 
    2 . 3 绝对不要清除对象 
    Java会自动帮助我们清除对象。 
    2 . 3 . 1 作用域 
    对于在作用域里定义的名字,作用域同时决定了它的“可见性”以及“存在时间”。例子: 

    int x = 12; 
    /* only x available */ 

    int q = 96; 
    /* both x & q available */ 

    /* only x available */ 
    /* q “out of scope” */ 
    } { 
    int x = 12; 

    int x = 96; /* illegal */ 


    2 . 3 . 2 对象的作用域 

    String s = new String("a string"); 
    } /* 作用域的终点 */ 
    句柄s 会在作用域的终点处消失。
    然而,s 指向的 String 对象依然占据着内存空间,Java 有一个特别的“垃圾收集器”,它会自动释放由那些闲置对象占据的内存, 
    2 . 4 新建数据类型:类 
    class ATypeName {
    /*类主体置于这里
    }
    用 new 创建这种类型的一个新对象:
    ATypeName a = new ATypeName(); 
    2 . 4 . 1 字段和方法 
    类里设置两种类型的元素:数据成员(“字段”)以及成员函数(“方法”)。
    数据成员是一种对象(句柄),也可以是主类型(并不是句柄)。
    如果是指向对象的一个句柄,则必须初始化那个句柄。 
    若是一种主类型,可在类定义位置直接初始化
    1. 主成员的默认值 
    主类型 默认值 
    Boolean false 
    Char '\u0000'(null) 
    byte (byte)0 
    short (short)0 
    int 0 
    long 0L 
    float 0.0f 
    double 0.0d 
    2 . 5 方法、自变量和返回值 
    “方法”(Method),代表“完成某事的途径”。
    返回类型 方法名( /* 自变量列表*/ ) {/* 方法主体 */} 
    int x = a.f(); 
    2 . 5 . 1 自变量列表 
    自变量列表规定了我们传送给方法的是什么信息。正如在 Java 其他地方处理对象时一样,我们实际传递的是“句柄”。
    对于前面提及的“特殊”数据类型 boolean,char,byte,short,int,long,,float 以及 double 来说是一个例外。
    2 . 6 构建 J a v a 程序 
    正式构建自己的第一个 Java 程序前,还有几个问题需要注意。 
    2 . 6 . 1 名字的可见性 
    如何区分两个名字,并防止两个名字互相冲突呢?
    C 语言里特别突出。因为程序未提供很好的名字管理方法。
    C++的类嵌套使用类里的函数,使其不至于同其他类里的嵌套函数名冲突。然而,C++仍然允许使用全局数据以及全局函数,所以仍然难以避免冲突。为解决这个问题,C++用额外的关键字引入了“命名空间”的概念。 
    Java 能完全避免这些问题。为了给一个库生成明确的名字,采用了与Internet 
    域名类似的名字。事实上,Java 的设计者鼓励程序员反转使用自己的Internet 域名,因为它们肯定是独一无二的。由于我的域名是BruceEckel.com,所以我的实用工具库就可命名为com.bruceeckel.utility.foibles。 
    2 . 6 . 2 使用其他组件 
    编译器就必须知道如何找到预先定义好的类?
    1在发出调用的那个相同的源码文件里
    2位于其他文件里
    3引用的类与现有类的名字发生了冲突。
    import 关键字准确告诉 Java 编译器我们希望的类是什么。import 的作用是指示编译器导入一个“包”
    import java.util.Vector; 
    import java.util.*; 
    2 . 6 . 3 s t a t i c 关键字 
    static数据或方法与类的对象无关,有些面向对象的语言使用了“类数据”和“类方法”这两个术语。
    例如,
    class StaticTest { 
    static int i = 47; 

    StaticTest st1 = new StaticTest(); 
    StaticTest st2 = new StaticTest(); 
    尽管制作了两个StaticTest 对象,但它们仍然只占据StaticTest.i 的一个存储空间。st1.i 还是 st2.i 都有同样的值 47,
    StaticTest.i++;
    此时, st1.i 还是 st2.i 的值都是48。 类似的逻辑也适用于静态方法。
    class StaticFun { 
    static void incr() { StaticTest.i++; } 

    两种调用静态方法的方式 
    StaticFun sf = new StaticFun(); sf.incr(); 
    或者,StaticFun.incr(); 
    静态,作用于数据成员,就会明确改变数据的创建方式,对方法来说,一项重要的用途是帮助我们在不必创建对象的前提下调用那个方法。
    注意静态方法不能调用非静态变量或方法。
     
    2 . 7 我们的第一个 J a v a 程序 
    2 . 8 注释和嵌入文档 
    /* 这是一段注释, 
    它跨越了多个行 */ 
    // 这是一条单行注释 
    2 . 8 . 1 注释文档 
    Javadoc,它采用了部分来自 Java 编译器的技术,查找我们置入程序的特殊注释标记。它不仅提取由这些标记指示的信息,也将毗邻注释的类名或方法名提取出来。javadoc 输出的是一个 HTML 文件 
    2 . 8 . 2 具体语法 
    /** 一个类注释 */ 
    public class docTest { 
    /** 一个变量注释 */ 
    public int i; 
    /** 一个方法注释 */ 
    public void f() {} 

    注意 javadoc 只能为 public(公共)和 protected(受保护)成员处理注释文档。这是客户程序员的希望。然而,所有类注释都会包含到输出结果里。 
    2 . 8 . 3 嵌入 H T M L 
    javadoc 将 HTML 命令传递给最终生成的 HTML 文档。这便使我们能够充分利用 HTML 的巨大威力。 
    /** 
    * 您<em>甚至</em>可以插入一个列表: 
    * <ol> 
    * <li> 项目一 
    * <li> 项目二 
    * <li> 项目三 
    * </ol> 
    */ 
    注意在文档注释中,位于一行最开头的星号会被javadoc 丢弃。同时丢弃的还有前导空格。javadoc 会对所有内容进行格式化,使其与标准的文档外观相符。不要将<h1>或<hr>这样的标题当作嵌入 HTML 使用,因为 javadoc 会插入自己的标题,我们给出的标题会与之冲撞。所有类型的注释文档——类、变量和方法——都支持嵌入 HTML。 
    2 . 8 . 4 @ s e e :引用其他类 
    @see 允许我们引用其他类里的文档。对于这个标记,javadoc 会生成相应的 HTML,将其直接链接到其他文档。格式如下: 
    @see 类名 
    @see 完整类名 
    @see 完整类名#方法名 
    每一格式都会在生成的文档里自动加入一个超链接的“See Also”(参见)条目。注意 javadoc 不会检查我们指定的超链接,不会验证它们是否有效。 
    2 . 8 . 5 类文档标记 
    随同嵌入 HTML 和@see 引用,类文档还可以包括用于版本信息以及作者姓名的标记。类文档亦可用于“接口”目的(本书后面会详细解释)。 
    1.@version 版本信息 
    “版本信息”代表任何适合作为版本说明的资料。若在 javadoc 命令行使用了“-version”标记,就会从生成的 HTML 文档里提取出版本信息。 
    2.@author 作者信息 
    包括您的姓名、电子函件地址或者其他任何适宜的资料。若在javadoc 命令行使用了“- author”标记,就会专门从生成的 HTML 文档里提取出作者信息。可为一系列作者使用多个这样的标记,但它们必须连续放置。全部作者信息会一起存入最终 HTML 代码的单独一个段落里。 
    2 . 8 . 6 变量文档标记 
    变量文档只能包括嵌入的 HTML 以及@see 引用。 
    2 . 8 . 7 方法文档标记 
    除嵌入 HTML 和@see 引用之外,方法还允许使用针对参数、返回值以及违例的文档标记。 
    1. @param 参数名 说明 
    “参数名”是指参数列表内的标识符,
    “说明”代表一些可延续到后续行内的说明文字。一旦遇到一个新文档标记,就认为前一个说明结束。可使用任意数量的说明,每个参数一个。 
    2. @return 说明 
    “说明”是指返回值的含义。
    3.@exception 完整类名 说明 
    “完整类名”明确指定了一个违例类的名字,它是在其他某个地方定义好的。而“说明”(同样可以延续到下面的行)告诉我们为什么这种特殊类型的违例会在方法调用中出现。 
    4. @deprecated 
    这是 Java 1.1 的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该方法时会收到编译器的警告。 
    2 . 8 . 8 文档示例 
    下面还是我们的第一个 Java 程序,只不过已加入了完整的文档注释: 
    //: Property.java 
    import java.util.*; 
    /** The first Thinking in Java example program. 
    * Lists system information on current machine. 
    * @author Bruce Eckel 
    * @author http://www.BruceEckel.com 
    * @version 1.0 
    */ 
    public class Property { 
    /** Sole entry point to class & application 
    * @param args array of string arguments 
    * @return No return value 
    * @exception exceptions No exceptions thrown 
    */ 
    public static void main(String[] args) { 
    System.out.println(new Date()); 
    Properties p = System.getProperties(); 
    p.list(System.out); 
    System.out.println("--- Memory Usage:"); 
    Runtime rt = Runtime.getRuntime(); 
    System.out.println("Total Memory = " 
    + rt.totalMemory() 
    + " Free Memory = " 
    + rt.freeMemory()); 
    } } ///:~ 
    第一行: 
    //: Property.java 
    采用了我自己的方法:将一个“:”作为特殊的记号,指出这是包含了源文件名字的一个注释行。最后一行也 用这样的一条注释结尾,它标志着源代码清单的结束。这样一来,可将代码从本书的正文中方便地提取出来,并用一个编译器检查。这方面的细节在第 17 章讲述。 
    2 . 9 编码样式 
    一个非正式的 Java 编程标准是大写一个类名的首字母。若类名由几个单词构成,那么把它们紧靠到一起(也就是说,不要用下划线来分隔名字)。此外,每个嵌入单词的首字母都采用大写形式。例如: 
    class AllTheColorsOfTheRainbow { // ...} 
    对于其他几乎所有内容:方法、字段(成员变量)以及对象句柄名称,可接受的样式与类样式差不多,只是标识符的第一个字母采用小写。例如: 
    class AllTheColorsOfTheRainbow { 
    int anIntegerRepresentingColors; 
    void changeTheHueOfTheColor(int newHue) { 
    // ... 

    // ... 

    当然,要注意用户也必须键入所有这些长名字,而且不能输错。 
    2 . 1 0 总结 
    通过本章的学习,大家已对语言的总体情况以及一些基本思想也有了一定程度的认识。 
      

  4.   

    因为不能copy样式,有兴趣的朋友可以去我的博客http://blog.csdn.net/cui_691
      

  5.   


    第 4 章 初始化和清除 
    “随着计算机的进步,‘不安全’的程序设计已成为造成编程代价高昂的罪魁祸首之一。”“初始化”和“清除”是这些安全问题的其中两个。许多 C 程序的错误都是由于程序员忘记初始化一个变量造成的。对于现成的库,若用户不知道如何初始化库的一个组件,就往往会出现这一类的错误。清除是另一个特殊的问题,因为用完一个元素后,由于不再关心,所以很容易把它忘记。这样一来,那个元素占用的资源会一直保留下去,极易产生资源(主要是内存)用尽的后果。C++为我们引入了“构建器”的概念。这是一种特殊的方法,在一个对象创建之后自动调用。Java 也沿用了这个概念,但新增了自己的“垃圾收集器”,能在资源不再需要的时候自动释放它们。本章将讨论初始化和清除的问题,以及 Java 如何提供它们的支持。 
    4 . 1 用构建器自动初始化 
    在创建的对象时,我们往往忘记了初始化的工作,怎么解决呢?
    构建器的目的是使对象得到正确的初始化。请注意所有方法首字母小写的编码规则并不适用于构建器。这是由于构建器的名字必须与类名完全相同! 
    tree t = new Tree(12); // 12 英尺高的树 
    另一种初始化方法initialize()——在概念上独立于定义内容。
    在 Java 中,定义和初始化属于统一的概念——两者缺一不可。 
    4 . 2 方法过载 
    我们说“洗衬衫”、“洗车”以及“洗狗”。但若强制象下面这样说,就显得很愚蠢:“我.洗衬衫()”、“我.洗车()”以及“我.洗狗()”。这里我们不需要独一无二的标识符
    由于构建器的名字由类名决定,所以只能有一个构建器名称。 Tree t1 = new Tree(12); 
    Tree t2 = new Tree(); 
    t1.info(); 
    t2.info("overloaded method"); 
     
    4 . 2 . 1 区分过载方法 
    若方法有同样的名字,Java 怎样知道我们指的哪一个方法呢?
    区分过载方法:独一无二的参数类型列表。 
    print("String first", 11); 
    print(99, "Int first"); 
    自变量顺序不同,可区分它们。但很难推测其方法的行为,不推荐使用。 
    4 . 2 . 2 主类型的过载 
    主(数据)类型能从小类型自动转变成大类型,反之需要强制类型转换。
    void doublePrint(double x) { syso(x); } 
    void intPrint(int x) { syso(x); } 
    main() { 
    doublePrint (5);double x=0;
    intPrint ((int)x);} 
    4 . 2 . 3 返回值过载 
    为什么不根据返回值对方法加以区分? 
    void f() {syso(1)} 
    int f() { syso(2)} 
    我们可能调用一个方法,只关系调用的过程sys(2),不关心返回值。我们会写
    f();那么 Java 怎样判断f()的具体调用方式呢?而且别人如何识别并理解代码呢? 
    4 . 2 . 4 默认构建器 
    若创建一个没有构建器的类,则编译程序会帮我们自动创建一个默认构建器。
    然而,如果已经定义了一个构建器(无论是否有自变量),编译程序都不会帮我们自动合成一个: 
    class Bush { 
    Bush(int i) {} 

    现在,假若使用下述代码: 
    new Bush(); 
    编译程序就会报告自己找不到一个相符的构建器。就好象我们没有设置任何构建器,编译程序会说:“你看来似乎需要一个构建器,所以让我们给你制造一个吧。”但假如我们写了一个构建器,编译程序就会说:“啊,你已写了一个构建器,所以我知道你想干什么;如果你不放置一个默认的,是由于你打算省略它。” 
    4 . 2 . 5 t h i s 关键字 
    class Banana { void f(int i) { /* ... */ } } 
    Banana a = new Banana(), b = new Banana(); 
    a.f(1); 
    b.f(2); 
    若只有一个名叫f()的方法,它怎样才能知道自己是为 a 还是为 b 调用的呢? 
    编译器会为做一些幕后工作,将前述的两个方法调用就变成了下面这样的形式: 
    Banana.f(a,1); 
    Banana.f(b,2); 
    这是内部的表达形式,我们并不能这样书写表达式,并试图让编译器接受它。但是,通过它可理解幕后到底发生了什么事情。假定我们在一个方法的内部,并希望获得当前对象的句柄。由于那个句柄是由编译器“秘密”传递的,所以没有标识符可用。然而,针对这一目的有个专用的关键字:this。this 关键字(注意只能在方法内部使用)可为已调用了其方法的那个对象生成相应的句柄。可象对待其他任何对象句柄一样对待这个句柄。
    但要注意,假若准备从某个类的一个方法内部调用该类的另一个方法,就不必使用this。只需简单地调用那个方法即可。当前的 this 句柄会自动应用于其他方法。所以我们能使用下面这样的代码: 
    class Apricot { 
    void pick() { /* ... */ } 
    void pit() { pick(); /* ... */ } 

    编译器能帮我们自动完成this.pick()
    this 关键字只能用于那些特殊的类——需明确使用当前对象的句柄。
    例如,假若您希望将返回当前对象的句柄,可使用return this 
    Leaf increment() { 
    i++; 
    return this; 

    main() { 
    Leaf x = new Leaf(); 
    x.increment().increment().increment().print(); 

    由于 increment()通过 this 关键字返回当前对象的句柄,所以可以方便地对同一个对象执行多项操作。 
    1. 在构建器里调用构建器 
    通常this 是指“当前对象”且它本身会产生当前对象的一个句柄。
    但在一个构建器里调用该类的另一个构建器,以避免写重复的代码。可用 this,此时它的含义是匹配参数列表相同的构建器,如下所示: 
    //: Flower.java 
    // Calling constructors with "this" 
    public class Flower { 
    String  s;
    Flower(int petals) { 
    System.out.println( "Constructor w/ int arg only, petalCount= "+ petalCount); 

    Flower(String s) { 
    this.s=s; //another used this
    System.out.println( "Constructor w/ String arg only, s=" + s); 

    Flower(String s, int petals) { 
    this(petals); 
    //this(petals); can’t call another

    void print(){
    //this(11); can’t be called in method
    }
    } ///:~ 
    由于参数s和成员s名字冲突,可用 this.s 来引用成员数据。
    Flower(String s,int petals)揭示:this 调用一个构建器,但不可调用两个
    print()中,编译器不允许从构建器之外的其他任何方法内部调用构建器。 
    2. static 的含义 
    static意味着没有 this,即不可从 static 方法内部调用this(非static)
    但反过来可以。 
    4 . 3 清除:收尾和垃圾收集 
    Java 可用垃圾收集器回收由不再使用的由new创建的对象占据的内存。
    现在假定对象分配了一个“特殊”内存区域,(没有在内存堆内)没使用 new。怎么办呢? finalize()用于清除此对象,它定义在Object类中。在理想情况下,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用 finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。它意味着在我们不再需要一个对象之前,有些行动是必须采取的,而且必须由自己来采取这些行动。例如,假设在对象创建过程中,它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像,那么它可能永远都不会被清除。若在finalize()里置入某种删除机制,那么假设对象被当作垃圾收掉了,图像首先会将自身从屏幕上移去。但若未被收掉,图像就会保留下来
    要记住重点是:我们的对象可能不会当作垃圾被收掉! 
    有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。则随着程序的退出,那些资源会返回给操作系统。这是一件好事情,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远 
    也不用支出这部分开销。 
    4 . 3 . 1 f i n a l i z e ( ) 用途何在 
    垃圾收集器会负责释放所有对象占据的内存,无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。Java 中的所有东西都是对象,所以这到底是怎么一回事呢? 
    有时需要采取与Java 的普通方法不同的一种方法,通过分配内存来做一些具有 C 风格的事情。这主要可以通过“固有方法”来进行,它是从Java 里调用非 Java 方法的一种方式。C 和 C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序,所以能够有效地调用任何东西。在非 Java 代码内部,也许能调用 C 的 malloc()系列函数,用它分配存储空间。而且除非调用了free(),否则存储空间不会得到释放,从而造成内存“漏洞”的出现。当然,free()是一个 C 和 C++函数,所以我们需要在 finalize()内部的一个固有方法中 调用它。 
    大家不必过多地使用finalize()。 
    4 . 3 . 2 必须执行清除 ?4 . 4 成员初始化 
    1编译器会要求方法的“局部”变量初始化吗?
    void f() { 
    int i; 
    i++; 

    答案是不允许,提示错误消息i尚未初始化。当然,编译器也可为i 赋予一个默认值,但它看起来更象一个程序员的失误,此时默认值反而会“帮倒忙”。若强迫程序员提供一个初始值,就往往能够帮他纠出程序里的“臭虫”。 2编译器会要求类的基本类型的字段初始化吗?
    由于任何方法都可以初始化或使用那个数据,所以在正式使用数据前,若还是强迫程序员将其初始化成一个适当的值,就可能不是一种实际的做法。然而,若为其赋予一个垃圾值,同样是非常不安全的。因此,一个类的所有基本类型数据成员都会保证获得一个初始值。可用下面这段小程序看到这些值: 
    //: InitialValues.java 
    // Shows default initial values 
    class Measurement {  
    boolean t; 
    char c; 
    byte b; 
    int i; 
    void print() { 
    System.out.println( 
    "Data type Inital value\n" + 
    "boolean " + t + "\n" + 
    "char " + c + "\n" + 
    "byte " + b + "\n" + 
    "int " + i + "\n" + 


    public class InitialValues { 
    public static void main(String[] args) { 
    new Measurement().print(); 

    } ///:~ 
    输入结果如下: 
    Data type Inital value 
    boolean false 
    char 
    byte 0 
    int 0 
    4 . 4 . 1 规定初始化 
    在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。
    class Measurement { 
    boolean b = true; 
    char c = 'x'; 
    byte B = 47; 
    short s = 0xff; 
    int i = 999; 
    long l = 1; 
    float f = 3.14f; 
    double d = 3.14159; 
    Depth o = new Depth(); 
    boolean b = true; 
    // . . . 
    若尚未为o 指定一个初始值,同时不顾一切地提前试用它,就会得到一条运行期错误提示,告诉你产生了名为“违例”(Exception)的一个错误
    class CInit { 
    int i = f(); 
    //... 
    也可以
    class CInit { 
    int i = f(); 
    int j = g(i); 
    //... 

    但这样做是非法的: 
    class CInit { 
    int j = g(i); 
    int i = f(); 
    //... 

    这正是编译器对“向前引用”感到不适应的一个地方,因为它与初始化的顺序有关,而不是与程序的编译方式有关。 
    4 . 4 . 2 构建器初始化 
    class Counter { 
    int i; 
    Counter() { i = 7; } 
    // . . . 
    那么 i 首先会初始化成零,然后变成 7。 
    1. 初始化顺序 
    变量的初始化顺序是怎样的呢?跟构造器和方法的顺序有关吗?
    在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。跟构造器和方法被定义的顺序无关。即使构造器和方法的定义夹杂在变量定义中间。
    例如: 
    //: OrderOfInitialization.java 
    // Demonstrates initialization order. 
    // When the constructor is called, to create a 
    // Tag object, you'll see a message: 
    class Tag { 
    Tag(int er) { 
    System.out.println("Tag(" + er + ")"); 


    class Card { 
    Tag t1 = new Tag(1); // Before constructor 
    Card() { 
    // Indicate we're in the constructor: 
    System.out.println("Card()"); 
    t3 = new Tag(33); // Re-initialize t3 

    Tag t2 = new Tag(2); // After constructor 
    void f() { 
    System.out.println("f()"); 

    Tag t3 = new Tag(3); // At end 

    public class OrderOfInitialization { 
    public static void main(String[] args) { 
    Card t = new Card(); 
    t.f(); // Shows that construction is done 

    } ///:~ 
    输入结果如下: 
    Tag(1) 
    Tag(2) 
    Tag(3) 
    Card() 
    Tag(33) 
    f() 
    变量调用顺序为:变量定义的顺序,构造器,构造器中变量的赋值,方法的调用
    2. 静态数据的初始化 
    请考虑对象的创建过程。一个名为 Dog 的类: 
    (1) 首次创建Dog对象或调用Dog的static字段/方法时,JVM动态加载 Dog.class
    (2) 加载Dog.class时,所有 static 初始化模块都会运行且化仅发生一次时候。 
    (3) 创建Dog对象时,首先会在内存堆(Heap)里分配存储空间。 
    (4) 存储空间会清为零,将Dog 中的所有基本类型设为它们的默认值
    (5) 进行字段定义时发生的所有初始化都会执行。 
    (6) 执行构建器。 
    3. 静态代码块 
    static { 
    i = 47; 

    这段代码仅执行一次——首次创建类的对象或调用类的static成员时
    4. 在代码块中非静态实例的初始化 
    public class ClassLoaderTest {
    A a1,a2;
    {
    a1=new A(1);
    a2=new A(2);
    System.out.println("a1&a2");
    }

    ClassLoaderTest(){
    System.out.println("new ClassLoaderTest");
    } public static void main(String[] args) {
    new ClassLoaderTest();
    }


    }
    class A{
    A(int i){
    System.out.println("new a"+i);
    }
    }输出结果:
    new a1
    new a2
    a1&a2
    new ClassLoaderTest
    4 . 5 数组初始化 
    4 . 5 . 1 多维数组 
    4 . 6 总结 
    作为初始化的一种具体操作形式,构建器应使大家明确感受到在语言中进行初始化的重要性。与 C++的程序设计一样,判断一个程序效率如何,关键是看是否由于变量的初始化不正确而造成了严重的编程错误(臭虫)。这些形式的错误很难发现,而且类似的问题也适用于不正确的清除或收尾工作。由于构建器使我们能 保证正确的初始化和清除(若没有正确的构建器调用,编译器不允许对象创建),所以能获得完全的控制权和安全性。在 C++中,与“构建”相反的“破坏”(Destruction)工作也是相当重要的,因为用new 创建的对象必须明确地清除。在 Java 中,垃圾收集器会自动为所有对象释放内存,所以 Java 中等价的清除方法并不是经常都需要用到的。如果不需要类似于构建器的行为,Java 的垃圾收集器可以极大简化编程工作,而且在内存的管理过程中增加更大的安全性。有些垃圾收集器甚至能清除其他资源,比如图形和文件句柄等。然而,垃圾收集器确实也增加了运行期的开销。但这种开销到底造成了多大的影响却是很难看出的,因为到目前为止,Java 解释器的总体运行速度仍然是比较慢的。随着这一情况的改观,我们应该能判断出垃圾收集器的开销是否使 Java 不适合做一些特定的工作(其中一个问题是垃圾收集器不可预测的性质)。由于所有对象都肯定能获得正确的构建,所以同这儿讲述的情况相比,构建器实际做的事情还要多得多。特别地,当我们通过“创作”或“继承”生成新类的时候,对构建的保证仍然有效,而且需要一些附加的语法来提供对它的支持。大家将在以后的章节里详细了解创作、继承以及它们对构建器造成的影响。 
    4 . 7 练习 
    (1) 用默认构建器创建一个类(没有自变量),用它打印一条消息。创建属于这个类的一个对象。 
    (2) 在练习 1 的基础上增加一个过载的构建器,令其采用一个 String 自变量,并随同自己的消息打印出来。 
    (3) 以练习 2 创建的类为基础上,创建属于它的对象句柄的一个数组,但不要实际创建对象并分配到数组里。运行程序时,注意是否打印出来自构建器调用的初始化消息。 
    (4) 创建同句柄数组联系起来的对象,最终完成练习3。 
    (5) 用自变量“before”,“after”和“none”运行程序,试验 Garbage.java。重复这个操作,观察是否从输出中看出了一些固定的模式。改变代码,使System.runFinalization()在 System.gc()之前调用,再观察结果。 
      

  6.   

    Reusing Classes 
    One of the most compelling features about Java is code reuse. But to be revolutionary, you’ve got to be able to do a lot more than copy code and change it. 
    可见,重用java最吸引人的特性之一。
    重复代码是bad smells,应该把重复的代码封装成一个方法,已达到重用的目的。封装代码可以使类变得更清晰,但你必须给它起个好名字。
    The trick is to use the classes without soiling the existing code. In this chapter you’ll see two ways to accomplish this. The first is quite straightforward: you simply create objects of your existing class inside the new class. This is called composition, because the new class is composed of objects of existing classes. You’re simply reusing the functionality of the code, not its form. 
    The second approach is more subtle. It creates a new class as a type of an existing class. You literally take the form of the existing class and add code to it without modifying the existing class. This technique is called inheritance, and the compiler does most of the work. Inheritance is one of the cornerstones of object-oriented programming, and has additional implications that will be explored in the Polymorphism chapter. 
    如何巧妙的使用类,又不污染已存在的代码呢?
    组合和继承,
    Composition syntax 组合语法
    Composition has been used quite frequently up to this point in the book. You simply place object references inside new classes. 
    组合就是在一个新类里引入对象引用,注意应用应尽可能定义为一个接口变量,实现解耦,spring的IoC。
    //: reusing/SprinklerSystem.java 
    // Composition for code reuse. 
    class WaterSource { 
    private String s; 
    WaterSource() { 
    System.out.println("WaterSource()"); 
    s = "Constructed"; 

    public String toString() { return s; } 

    public class SprinklerSystem { 
    private String valve1, valve2, valve3, valve4; 
    private WaterSource source = new WaterSource(); 
    private int i; 
    private float f; 
    public String toString() { 
    return "valve1 = " + valve1 + " " + 
    "valve2 = " + valve2 + " " + 
    "valve3 = " + valve3 + " " + 
    "valve4 = " + valve4 + "\n" + 
    "i = " + i + " " + "f = " + f + " " + 
    "source = " + source; 

    public static void main(String[] args) { 
    SprinklerSystem sprinklers = new SprinklerSystem(); 
    System.out.println(sprinklers); 

    } /* Output: 
    WaterSource() 
    valve1 = null valve2 = null valve3 = null valve4 = null 
    i = 0 f = 0.0 source = Constructed 
    *///: 
    One of the methods defined in both classes is special: toString( ). Every non-primitive object has a toString( ) method, and it’s called in special situations when the compiler wants a String but it has an object. So in the expression in SprinklerSystem.toString( ): 
    "source = " + source; 
    the compiler sees you trying to add a String object ("source = ") to a WaterSource. Because you can only “add” a String to another String, it says “I’ll turn source into a String by calling toString( )!” After doing this it can combine the two Strings and pass the resulting String to System.out.println( ) (or equivalently, this book’s print() and printnb( ) static methods). Any time you want to allow this behavior with a class you create, you need only write a toString( ) method. 
    toString()方法是一个特殊的方法,它是Object的一个方法,感兴趣的朋友可以查查JDK。此方法会在一种特殊的情况下会被调用,当编译器需要一个字符串时,可它只有一个对象。举例:"source = " + source; 此时会调用source.toString().Primitives that are fields in a class are automatically initialized to zero, as noted in the Everything Is an Object chapter. But the object references are initialized to null, and if you try to call methods for any of them, you’ll get an exception-a runtime error. Conveniently, you can still print a null reference without throwing an exception. 
    It makes sense that the compiler doesn’t just create a default object for every reference, because that would incur unnecessary overhead in many cases. If you want the references initialized, you can do it: 
    1. At the point the objects are defined. This means that they’ll always be initialized before the constructor is called. 
    2. In the constructor for that class. 
    3.Right before you actually need to use the object. This is often called lazy initialization. It can reduce overhead in situations where object creation is expensive and the object doesn’t need to be created every time. 
    4. Using instance initialization. 
    引用合适该初始化?有以下四种
    1. 在定义时
    2. 在构造函数中
    3. 在使用时,即我们常说的懒加载,如果不用就不初始化。
    4. 使用实例初始化,翻译过来不理解什么意思,看以下代码,应该是在代码段中初始化。
    All four approaches are shown here: 
    //: reusing/Bath.java 
    // Constructor initialization with composition. 
    import static net.mindview.util.Print.*; 
    class Soap { 
    private String s; 
    Soap() { 
    print("Soap()"); 
    s = "Constructed"; 

    public String toString() { return s; } 

    public class Bath { 
    private String // Initializing at point of definition: 在定义时
    s1 = "Happy", 
    s2 = "Happy", 
    s3, s4; 
    private Soap castille; 
    private int i; 
    private float toy; 
    public Bath() { 
    print("Inside Bath()"); 
    s3 = "Joy"; //构造函数中toy = 3.14f; 
    castille = new Soap(); 

    // Instance initialization: 使用实例初始化{ i = 47; } 
    public String toString() { 
    if(s4 == null) // Delayed initialization: 懒加载
    s4 = "Joy"; 
    return 
    "s1 = " + s1 + "\n" + 
    "s2 = " + s2 + "\n" + 
    "s3 = " + s3 + "\n" + 
    "s4 = " + s4 + "\n" + 
    "i = " + i + "\n" + 
    "toy = " + toy + "\n" + 
    "castille = " + castille; 

    public static void main(String[] args) { 
    Bath b = new Bath(); 
    print(b); 

    } /* Output: 
    注意执行顺序
    创建对象时

    Inside Bath() 
    Soap() 
    打印对象时
    s1 = Happy 
    s2 = Happy 
    s3 = Joy 
    s4 = Joy 
    i = 47 
    toy = 3.14 
    castille = Constructed 
    *///:~ 
    Note that in the Bath constructor, a statement is executed before any of the initializations take place. When you don’t initialize at the point of definition, there’s still no guarantee that you’ll perform any initialization before you send a message to an object reference—except for the inevitable run-time exception. 
    When toString( ) is called it fills in s4 so that all the fields are properly initialized by the time they are used. 
    Exercise 1: (2) Create a simple class. Inside a second class, define a reference to an object of the first class. Use lazy initialization to instantiate this object.
      

  7.   

    不用把那些东西COPY出来吸引人吧
      

  8.   

    由于copy出来,效果实在很差,决定结贴。
    关注java编程思想精简的朋友请去我博客。
    后续会陆续更新。
      

  9.   

    由于copy出来,效果实在很差,决定结贴。
    关注java编程思想精简的朋友请去我博客。
    后续会陆续更新。