《Thinking in Java》读书笔记

第1章 对象入门

 

基本概念:

Java的Project name和工程目录名对应,每个Project下又可以包含若干package,每个package名又跟工程目录下的同名目录对应,在每个package可以建若干class,每个class对应两个文件,类名.java和类名.class,前者是编译过的,后者是源代码文件。通过Eclipse文件->Export,我们可以把整个工程(即整个工程目录)下的所有.class文件打包成jar文件。当我们要使用jar文件中的类时,可以把jar文件放到Eclipse可以找到的地方,或者直接通过Import导入到工程中,通过在代码中import,我们就可以使用了。

发布时,只要把jar文件发布出去就行。

 

 

 

问题:

1.package怎么用?

2.如何设置断点和调试?

 

 

 

Java编程规范

1.包名都是小写字母开头;方法都是第一个单词首字母小写其余单词首字母大写;类名所有单词首字母大写.

 

Java常用类及方法

System(println、getProperties、exit、gc、runFinalization、runFinalizersOnExit)

Properties(list)

Runtime(getRuntime、totalMemory、freeMemory)

Date

Random(nextInt、nextFloat)

Integer(equals)

Math(random、abs)

String(equals、indexOf、subString)

Exception

File(list、getName)

Vector(addElement、elements、elementAt、size)

Enumeration(hasMoreElements、nextElement)

Class(forName、toString、newInstance、getInterfaces、getSuperclass、isInterface)

Object(getClass、clone)

Hashtable(put、get)

Method(getMethods)

Constructor(getConstructors)

 

 

第2章 一切都是对象

Java是一种更纯粹的面向对象程序设计语言

Java语言首先便假定了我们只希望进行面向对象的程序设计。

任何东西都可看作对象

 

2.1 用句柄操纵对象

但要注意,尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“句柄”(Handle)。在其他Java参考书里,还可看到有的人将其称作一个“引用”。

 

我们实际操纵的是遥控板(句柄),再由遥控板自己操纵电视机(对象)。如果要在房间里四处走走,并想保持对电视机的控制,那么手上拿着的是遥控板,而非电视机。

此外,即使没有电视机,遥控板亦可独立存在。也就是说,只是由于拥有一个句柄,并不表示必须有一个对象同它连接。

 

String s;

但这里创建的只是句柄,并不是对象。若此时向s发送一条消息,就会获得一个错误(运行期)。这是由于s实际并未与任何东西连接(即“没有电视机”)。因此,一种更安全的做法是:创建一个句柄时,记住无论如何都进行初始化:

String s = "asdf";//这是一种不好的方法,应该用2.2中的方法

 

2.2 所有对象都必须创建

创建句柄时,我们希望它同一个新对象连接。通常用new关键字达到这一目的。new的意思是:“把我变成这些对象的一种新类型”。所以在上面的例子中,可以说:

String s = new String("asdf");//任何对象都应该new出来

 

2.2.1 保存到什么地方

 

这一限制无疑影响了程序的灵活性,所以尽管有些Java数据要保存在堆栈里--特别是对象句柄,但Java对象并不放到其中。

 

(3) 堆。一种常规用途的内存池(也在RAM区域),其中保存了Java对象。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!

 

可用static关键字指出一个对象的特定元素是静态的。但Java对象本身永远都不会置入静态存储空间。

 

Java的基本数据类型会经常使用,所以在堆中分配效率低下。所以,对于基本类型不是用new创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。

 

基本类型有:(和C++不同的是各种类型的大小是固定的,便于移植)

boolean(1bit) char(16bit) byte(8bit) short(16bit)int(32bit) long(64bit) float(32bit) double(64bit) void

 

数值类型全都是有符号(正负号)的,所以不必费劲寻找没有符号的类型。

基本数据类型也拥有自己的“封装器”(wrapper)类。这意味着假如想让堆内一个非主要对象表示那个主类型,就要使用对应的封装器。例如:

char c = 'x';

Character C = new Character('c');

也可以直接使用:

Character C = new Character('x');

这样做的原因将在以后的章节里解释。

1. 高精度数字

BigInteger支持任意精度的整数。也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢失任何信息。

BigDecimal支持任意精度的定点数字。例如,可用它进行精确的币值计算。

 

也就是说,能对int或float做的事情,对BigInteger和BigDecimal一样可以做。只是必须使用方法调用,不能使用运算符。此外,由于牵涉更多,所以运算速度会慢一些。我们牺牲了速度,但换来了精度。

 

2.2.3 Java的数组

Java的一项主要设计目标就是安全性。所以在C和C++里困扰程序员的许多问题都未在Java里重复。一个Java可以保证被初始化,而且不可在它的范围之外访问。由于系统自动进行范围检查,所以必然要付出一些代价:针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。但由此换回的是更高的安全性,以及更高的工作效率。为此付出少许代价是值得的。

创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦Java看到null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为null的一个句柄,就会在运行期报告问题。因此,典型的数组错误在Java里就得到了避免。

也可以创建主类型数组。同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。

 

2.3 绝对不要清除对象

 

2.3.1 作用域

注意尽管在C和C++里是合法的,但在Java里不能象下面这样书写代码:

{

  int x =12;

  {

    int x =96; /* illegal */

  }

}

 

编译器会认为变量x已被定义。所以C和C++能将一个变量“隐藏”在一个更大的作用域里。但这种做法在Java里是不允许的,因为Java的设计者认为这样做使程序产生了混淆。

 

 

2.3.2 对象的作用域

Java对象不具备与基本数据类型一样的存在时间。用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:

 

{

String s = new String("a string");

} /* 作用域的终点 */

 

那么句柄s会在作用域的终点处消失。然而,s指向的String对象依然占据着内存空间。

 

Java有一个特别的“垃圾收集器”,它会查找用new创建的所有对象,并辨别其中哪些不再被引用。随后,它会自动释放由那些闲置对象占据的内存,以便能由新对象使用。这意味着我们根本不必操心内存的回收问题。只需简单地创建对象,一旦不再需要它们,它们就会自动离去。这样做可防止在C++里很常见的一个编程问题:由于程序员忘记释放内存造成的“内存溢出”。

 

2.4 新建数据类型:类(和C++基本相同)

我们在Java里的全部工作就是定义类、制作那些类的对象以及将消息发给那些对象

 

但若是一种基本数据类型,则可在类定义位置直接初始化.

 

类中的基本数据类型如果没有初始化都有默认值,这和C++是不同的。默认值如下:

Boolean false

Char '\u0000'(null)

byte (byte)0

short (short)0

int 0

long 0L

float 0.0f

double 0.0d

 

在Java中,数据成员叫“字段”,成员函数叫“方法”。和C++区别一下。

 

int x = a.f();

象这样调用一个方法的行动通常叫作“向对象发送一条消息”。在上面的例子中,消息是f(),而对象是a。面向对象的程序设计通常简单地归纳为“向对象发送消息”。

 

③:正如马上就要学到的那样,“静态”方法可针对类调用,毋需一个对象。

 

2.6.2 使用其他组件

但请记住,Java的所有代码都必须写入一个类中。

 

大多数时候,我们直接采用来自标准Java库的组件(部件)即可,它们是与编译器配套提供的。

例:

import java.util.Vector;

它的作用是告诉编译器我们想使用Java的Vector类。然而,util包含了数量众多的类,我们有时希望使用其中的几个,同时不想一个个明确地声明它们。为达到这个目的,可使用“*”通配符。如下所示:

import java.util.*;

需导入一系列类时,采用的通常是这个办法。应尽量避免一个一个地导入类。

 

对方法来说,static一项重要的用途就是帮助我们在不必创建对象的前提下调用那个方法。正如以后会看到的那样,这一点是至关重要的--特别是在定义程序运行入口方法main()的时候。

 

2.7 我们的第一个Java程序

// Property.java

import java.util.*;

 

public class Property {

  publicstatic void main(String[] args) {

   System.out.println(new Date());

   Properties p = System.getProperties();

   p.list(System.out);

   System.out.println("--- Memory Usage:");

    Runtimert = Runtime.getRuntime();

   System.out.println("Total Memory = "

                       + rt.totalMemory()  //这种“自动类型转换”可划入“运算符过载”处理的范畴。

                       + " Free Memory ="

                       + rt.freeMemory());

  }

}

 

⑤:在某些编程环境里,程序会在屏幕上一切而过,甚至没机会看到结果。可将下面这段代码置于main()的末尾,用它暂停输出:

try {

Thread.currentThread().sleep(5 * 1000);

} catch(InterruptedException e) {}

}

它的作用是暂停输出5秒钟。这段代码涉及的一些概念要到本书后面才会讲到。所以目前不必深究,只知道它是让程序暂停的一个技巧便可。

由于java.lang默认进入每个Java代码文件,所以这些类在任何时候都可直接使用。

 

如果不清楚一个特定的类在哪个类库里,或者想检视所有的类,可在Java用户文档里选择“Class Hierarchy”(类分级结构)。

 

注意javadoc只能为public(公共)和protected(受保护)成员处理注释文档。“private”(私有)和“友好”(详见5章)成员的注释会被忽略,我们看不到任何输出(也可以用-private标记包括private成员)。这样做是有道理的,因为只有public和protected成员才可在文件之外使用,这是客户程序员的希望。

 

2.9 编码样式

一个非正式的Java编程标准是大写一个类名的首字母。

于其他几乎所有内容:方法、字段(成员变量)以及对象句柄名称,可接受的样式与类样式差不多,只是标识符的第一个字母采用小写。

 

 

第3章 控制程序流程

 

Java中的数据成员和方法默认是protected属性。这和在C++(默认为private属性)中是不一样的。

 

 

比较两个对象:

public class Equivalence {

  publicstatic void main(String[] args) {

    Integern1 = new Integer(47);

    Integern2 = new Integer(47);

   System.out.println(n1 == n2);

   System.out.println(n1 != n2);

  }

} ///:~

 

其中,表达式System.out.println(n1 == n2)可打印出内部的布尔比较结果。一般人都会认为输出结果肯定先是true,再是false,因为两个Integer对象都是相同的。但尽管对象的内容相同,句柄却是不同的,而==和!=比较的正好就是对象句柄。所以输出结果实际上先是false,再是true。这自然会使第一次接触的人感到惊奇。

若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。下面举例说明如何使用:

 

 

//: EqualsMethod.java

 

public class EqualsMethod {

  publicstatic void main(String[] args) {

    Integern1 = new Integer(47);

    Integern2 = new Integer(47);

   System.out.println(n1.equals(n2));

  }

} ///:~

 

正如我们预计的那样,此时得到的结果是true。但事情并未到此结束!假设您创建了自己的类,就象下面这样:

 

 

//: EqualsMethod2.java

 

class Value {

  int i;

}

 

public class EqualsMethod2 {

  publicstatic void main(String[] args) {

    Value v1= new Value();

    Value v2= new Value();

    v1.i =v2.i = 100;

   System.out.println(v1.equals(v2));//默认调用booleanjava.lang.Object.equals(Object obj),所以结果还是false。除非在Value //中改写equals函数。

  }

} ///:~

 

此时的结果又变回了false!这是由于equals()的默认行为是比较句柄。所以除非在自己的新类中改变了equals(),否则不可能表现出我们希望的行为。不幸的是,要到第7章才会学习如何改变行为。但要注意equals()的这种行为方式同时或许能够避免一些“灾难”性的事件。

大多数Java类库都实现了equals(),所以它实际比较的是对象的内容,而非它们的句柄。

 

3.1.6 逻辑运算符

与在C及C++中不同,不可将一个非布尔值当作布尔值在逻辑表达式中使用。若这样做,就会发现尝试失败,并用一个“//!”标出。

 

例:

 prt("i> j is " + (i > j)); //注释:(i > j)会自动转换为false或者true和前面的字符串连接

结果:

i > j is false

 

3.1.8 移位运算符

“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。但在进行“无符号”右移位时,也可能遇到一个问题:若对byte、char、short值进行右移位运算,得到的可能不是正确的结果(Java1.0和Java 1.1特别突出)。因为此时“零扩展”不会发生,它仍然采用“符号扩展”。

 

>>>运算符是C或C++没有的。其他情况和C++中一样。

 

3.1.10 逗号运算符

在Java里需要用到逗号的唯一场所就是for循环.

注意,在Java中char占2个字节,并且永远为正。byte为一个字节,有正有负。

 

3.1.11 字串运算符+

我们注意到运用“String +”时一些有趣的现象。若表达式以一个String起头,那么后续所有运算对象都必须是字串。如下所示:

 

int x = 0, y = 1, z = 2;

String sString = "x, y, z ";

System.out.println(sString + x + y + z);

 

在这里,Java编译程序会将x,y和z转换成它们的字串形式,而不是先把它们加到一起。然而,如果使用下述语句:

 

System.out.println(x + sString);

 

那么早期版本的Java就会提示出错(以后的版本能将x转换成一个字串)。因此,如果想通过“加号”连接字串(使用Java的早期版本),请务必保证第一个元素是字串(或加上引号的一系列字符,编译能将其识别成一个字串)。

 

3.1.12 运算符常规操作规则

while(x = y) {

//...

}

 

例:

public class MyProject {

     public static void main(String[] args) {

     for( char c = 0; c < 128; c++)

       if (c != 26 )  // ANSI Clear screen

         System.out.println(

           "value: " + (int)c + //注意,这儿输出数字

           " character: " + c); //注意,这儿输出字符

     }

   } ///:~

 

程序的意图是测试是否“相等”(==),而不是进行赋值操作。在C和C++中,若y是一个非零值,那么这种赋值的结果肯定是true。这样使可能得到一个无限循环。在Java里,这个表达式的结果并不是布尔值,而编译器期望的是一个布尔值,而且不会从一个int数值中转换得来。所以在编译时,系统就会提示出现错误,有效地阻止我们进一步运行程序。但是如果x,y都是boolean型,有可能仍然会出现我们所不期望的结果!

 

Java允许我们将任何主类型“转型”为其他任何一种主类型,但布尔值(boolean)要除外,后者根本不允许进行任何转型处理。“类”不允许进行造型。为了将一种类转换成另一种,必须采用特殊的方法。

 

3.1.14 Java没有“sizeof”

Java不需要sizeof()运算符来满足这方面的需要,因为所有数据类型在所有机器的大小都是相同的。我们不必考虑移植问题--Java本身就是一种“与平台无关”的语言。

 

3.2 执行控制

然而,Java并不支持非常有害的goto(它仍是解决某些特殊问题的权宜之计)。仍然可以进行象goto那样的跳转,但比典型的goto要局限多了。

 

注意Java不允许我们将一个数字作为布尔值使用,即使它在C和C++里是允许的(真是非零,而假是零)。若想在一次布尔测试中使用一个非布尔值--比如在if(a)里,那么首先必须用一个条件表达式将其转换成一个布尔值,例如if(a!=0)。

 

可在for语句里定义多个变量,但它们必须具有同样的类型:

 

 

for(int i = 0, j = 1;

    i <10 && j != 11;

    i++,j++)

 /* body offor loop */;

 

其中,for语句内的int定义同时覆盖了i和j。只有for循环才具备在控制表达式里定义变量的能力。对于其他任何条件或循环语句,都不可采用这种方法。

 

1. 臭名昭著的“goto”

对Java来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方--在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于break和continue关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方。如下所示:

label1:

外部循环{

内部循环{

//...

break; //跳出内部循环

//...

continue; //继续内部循环

//...

continue label1; //继续外部循环

//...

break label1; //跳出外部循环

}

}

 

大家要记住的重点是:在Java里唯一需要用到标签的地方就是拥有嵌套循环,而且想中断或继续多个嵌套级别的时候。

但幸运的是,Java标签不会造成这方面的问题,因为它们的活动场所已被限死,不可通过特别的方式到处传递程序的控制权。

 

 

第4章 初始化和清除

注意:

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。那些变量仍会在调用任何方法之前得到初始化--甚至在构建器调用之前。

 

static初始化只有在必要的时候才会进行。如果不创建一个Table对象,而且永远都不引用Table.b1或Table.b2,那么staticBowl b1和b2永远都不会创建。然而,只有在创建了第一个Table对象之后(或者发生了第一次static访问,包括static数据和static方法),它们才会创建。在那以后,static对象不会重新初始化。

调用static数据或者方法时,语法为类名.数据、类名.方法。可见,Java中的static和C++中的有很大区别.

 

初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static对象。

 

int[] array=new int[i];//比起C++来,Java中的数组更加灵活,至少它可以在运行时决定数组的大小,而C++中不能。

 

尽管可用this调用一个构建器,但不可调用两个。除此以外,构建器调用必须是我们做的第一件事情,否则会收到编译程序的报错信息。

 //:Flower.java

// Calling constructors with "this"

 

public class Flower {

  privateint petalCount = 0;

  privateString s = new String("null");

  Flower(intpetals) {

   petalCount = petals;

   System.out.println(

     "Constructor w/ int arg only, petalCount= "

      +petalCount);

  }

 Flower(String ss) {

   System.out.println(

     "Constructor w/ String arg only, s=" + ss);

    s = ss;

  }

 Flower(String s, int petals) {

   this(petals);

//!   this(s); // Can't call two!

    this.s =s; // Another use of "this".如果不加this的话,两个s都是形参的s。这和在C++中是一样的。

   System.out.println("String & int args");

  }

  Flower() {

   this("hi", 47);

   System.out.println(

     "default constructor (no args)");

  }

  voidprint() {

//!   this(11); // Not inside non-constructor!

   System.out.println(

     "petalCount = " + petalCount + " s = "+ s);

  }

  publicstatic void main(String[] args) {

    Flower x= new Flower();

   x.print();

  }

} ///:~

 

 

这个例子也向大家展示了this的另一项用途。由于自变量s(也就是形参s)的名字以及成员数据s的名字是相同的,所以会出现混淆。为解决这个问题,可用this.s来引用成员数据。

 

4.3 清除:收尾和垃圾收集

为解决这个问题,Java提供了一个名为finalize()的方法,可为我们的类定义它。在理想情况下,它的工作原理应该是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。

但也是一个潜在的编程陷阱,因为有些程序员(特别是在C++开发背景的)刚开始可能会错误认为它就是在C++中为“破坏器”(Destructor)使用的finalize()--破坏(清除)一个对象的时候,肯定会调用这个函数。但在这里有必要区分一下C++和Java的区别,因为C++的对象肯定会被清除(排开编程错误的因素),而Java对象并非肯定能作为垃圾被“收集”去。或者换句话说:

 

垃圾收集并不等于“破坏”!

 

若能时刻牢记这一点,踩到陷阱的可能性就会大大减少。它意味着在我们不再需要一个对象之前,有些行动是必须采取的,而且必须由自己来采取这些行动。Java并未提供“破坏器”或者类似的概念,所以必须创建一个原始的方法,用它来进行这种清除。例如,假设在对象创建过程中,它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像,那么它可能永远都不会被清除。若在finalize()里置入某种删除机制,那么假设对象被当作垃圾收掉了,图像首先会将自身从屏幕上移去。但若未被收掉,图像就会保留下来。所以要记住的第二个重点是:

 

我们的对象可能不会当作垃圾被收掉!

 

有时可能发现一个对象的存储空间永远都不会释放,因为自己的程序永远都接近于用光空间的临界点。若程序执行结束,而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间,则随着程序的退出,那些资源会返回给操作系统。这是一件好事情,因为垃圾收集本身也要消耗一些开销。如永远都不用它,那么永远也不用支出这部分开销。

 

4.3.1 finalize()用途何在

此时,大家可能已相信了自己应该将finalize()作为一种常规用途的清除方法使用。它有什么好处呢?

要记住的第三个重点是:

 

垃圾收集只跟内存有关!

 

也就是说,垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。

 

读完上述文字后,大家或许已弄清楚了自己不必过多地使用finalize()。这个思想是正确的;它并不是进行普通清除工作的理想场所。那么,普通的清除工作应在何处进行呢?

 

所以如果站在比较简化的立场,我们可以说正是由于存在垃圾收集机制,所以Java没有破坏器。然而,随着以后学习的深入,就会知道垃圾收集器的存在并不能完全消除对破坏器的需要,或者说不能消除对破坏器代表的那种机制的需要(而且绝对不能直接调用finalize(),所以应尽量避免用它)。若希望执行除释放存储空间之外的其他某种形式的清除工作,仍然必须调用Java中的一个方法。它等价于C++的破坏器,只是没后者方便。

finalize()最有用处的地方之一是观察垃圾收集的过程。

 

为强制进行收尾工作,可先调用System.gc(),再调用System.runFinalization()。这样可清除到目前为止没有使用的所有对象。

 

void f() {

int i;

i++;

}

 

就会收到一条出错提示消息,告诉你i可能尚未初始化。

一个类的所有基本类型数据成员都会保证获得一个初始值。

 

class Counter {

int i;

Counter() { i = 7; }

// . . .

 

那么i首先会初始化成零,然后变成7。

 

3. 明确进行的静态初始化

Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:

 

 

class Spoon {

  static inti;

  static {

    i = 47;

  }

  // . . .

 

尽管看起来象个方法,但它实际只是一个static关键字,后面跟随一个方法主体。与其他static初始化一样,这段代码仅执行一次--首次生成那个类的一个对象时,或者首次访问属于那个类的一个static成员时(即便从未生成过那个类的对象)。

 

4. 非静态实例的初始化

针对每个对象的非静态变量的初始化,Java 1.1提供了一种类似的语法格式。下面是一个例子:

 

 

//: Mugs.java

// Java 1.1 "Instance Initialization"

 

class Mug {

  Mug(intmarker) {

   System.out.println("Mug(" + marker + ")");

  }

  void f(intmarker) {

   System.out.println("f(" + marker + ")");

  }

}

 

public class Mugs {

  Mug c1;

  Mug c2;

  {

    c1 = newMug(1);

    c2 = newMug(2);

   System.out.println("c1 & c2 initialized");

  }

  Mugs() {

   System.out.println("Mugs()");

  }

  publicstatic void main(String[] args) {

   System.out.println("Inside main()");

    Mugs x =new Mugs();

  }

} ///:~

 

大家可看到实例初始化从句:

 

 

  {

    c1 = newMug(1);

    c2 = newMug(2);

   System.out.println("c1 & c2 initialized");

  }

 

它看起来与静态初始化从句极其相似,只是static关键字从里面消失了。为支持对“匿名内部类”的初始化(参见第7章),必须采用这一语法格式。

 

4.5 数组初始化

在Java中,可以将一个数组赋值给另一个数组。

 int[] a1 ={ 1, 2, 3, 4, 5 };

    int[]a2;

    a2 =a1;//可以这样

注意:java中的数组有个length方法,可以返回数组有几个元素。并且就算越界了,会引起一个运行时错误。不会像C++中那么糟糕。

 

4.5.1 多维数组

int[][] a1 = {

      { 1,2, 3, },

      { 4,5, 6, },

    };

 

int[][][] a2 = new int[2][2][4];

 

有一个问题没有解决:如何才能完成C++中析构函数的功能???????

 

 

第5章 隐藏实施过程

 

注意:

Java中的protected和C++中的有点不一样.

private成员表示只有本类的方法才能访问它.

public成员表示无论谁都可以通过类名+成员名访问它.

protected成员表示除了本包的类能够通过类名+成员名访问它之外,包外的类都不能访问它,除非包外的类继承自它所在的类.

 

 

import就相当于C++里面的包涵头文件。

 

为Java创建一个源码文件的时候,它通常叫作一个“编辑单元”(有时也叫作“翻译单元”)。每个编译单元都必须有一个以.java结尾的名字。而且在编译单元的内部,可以有一个公共(public)类,它必须拥有与文件相同的名字(包括大小写形式,但排除.java文件扩展名)。如果不这样做,编译器就会报告出错。每个编译单元内都只能有一个public类(同样地,否则编译器会报告出错)。那个编译单元剩下的类(如果有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主public类的“支撑”类组成。

 

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

package mypackage; //将本类加入指定的名字空间,当然还需要放到实际的目录。就和系统的同样道理。

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

 

编译器遇到import语句后,它会搜索由CLASSPATH指定的目录,查找子目录com\bruceeckel\util,然后查找名称适当的已编译文件(对于Vector是Vector.class,对于List则是List.class)。

 

Java中类的默认访问属性是protected.

 

 

第6章 类再生

包含两种方法:组合和继承

下面就是组合:

 classWaterSource {

  privateString s;   //相当于在WaterSource 中加入了s

 WaterSource() {

   System.out.println("WaterSource()");

    s = newString("Constructed");

  }

  publicString toString() { return s; }

}

 

下面的就是继承。它和C++相比简单许多,因为它只有C++中的public继承,而没有private,protected继承。

class Art {

  Art() {

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

  }

}

 

class Drawing extends Art { //注意继承的语法,是用extends而不是和C++中一样。

  Drawing(){

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

  }

}

 

如何调用的基类的构造函数?

class Game {

  Game(inti) {

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

  }

}

 

class BoardGame extends Game {

 BoardGame(int i) {

   super(i);   //这就是调用基类构造函数的方法。注意,和C++中有点不一样

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

  }

}

 

如何完成C++中的析构函数功能?(确保正确的清除)

答:

public static void main(String[] args) {

   CADSystem x = new CADSystem(47);

    try {

      //Code and exception handling...

    }finally {

     x.cleanup(); //cleanup无论如何都会被执行

    }

  }

该警戒区后面跟随的finally从句的代码肯定会得以执行--不管try块到底存不存在(通过违例控制技术,try块可有多种不寻常的应用)。

 

1. 垃圾收集的顺序

不能指望自己能确切知道何时会开始垃圾收集。垃圾收集器可能永远不会得到调用。即使得到调用,它也可能以自己愿意的任何顺序回收对象。除此以外,Java1.0实现的垃圾收集器机制通常不会调用finalize()方法。除内存的回收以外,其他任何东西都最好不要依赖垃圾收集器进行回收。若想明确地清除什么,请制作自己的清除方法,而且不要依赖finalize()。然而正如以前指出的那样,可强迫Java1.1调用所有收尾模块(Finalizer)。

 

2.如何决定用继承还是用组合(和C++中一样)

 

6.8 final关键字(对应C++中的const)

 

对于基本数据类型,final会将值变成一个常数;但对于对象句柄,final会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。但指向的对象可以改变。

final int i1 = 9;

final Value v2 = new Value();

 

空白final:

尽管被声明成final,但却未得到一个初始值。无论在哪种情况下,空白final都必须在实际使用前得到正确的初始化。而且编译器会主动保证这一规定得以贯彻(当然,如果没有初始化编译器会报错)。

class BlankFinal {

  final intj; // Blank final

  finalPoppet p; // Blank final handle

  // Blankfinals MUST be initialized

  // in theconstructor:

 BlankFinal() {

    j = 1;// Initialize blank final

    p = newPoppet();

  }

 

3. final形式参数和C++中一样,就不用说了。

 

4.final成员方法

之所以要使用final方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。(和C++有点不同)

 

类内所有private方法都自动成为final。

只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略方法调用机制而采取的常规代码插入方法。和内联函数类似。

 

6.8.3 final类

如果说整个类都是final(在它的定义前冠以final关键字),就表明自己不希望从这个类继承,或者不允许其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。

除此以外,我们或许还考虑到执行效率的问题,并想确保涉及这个类各对象的所有行动都要尽可能地有效。如下所示:

final class Dinosaur {

  int i = 7;

  int j = 1;

  SmallBrainx = new SmallBrain();

  void f(){}

}//和C++中不同的是final类并不代表类中的成员数据不能改变。可以改变i,j

个final类中的所有方法都默认为final。因为此时再也无法覆盖它们。

 

除非真的需要代码,否则那个文件是不会载入的。通常,我们可认为除非那个类的一个对象构造完毕,否则代码不会真的载入。

 

第7章 多态性

 

Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。

 

为什么要把一个方法声明成final呢?正如上一章指出的那样,它能防止其他人覆盖那个方法。但也许更重要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可为final方法调用生成效率更高的代码。

 

 

针对这个问题,Java专门提供了一种机制,名为“抽象方法”。它属于一种不完整的方法,只含有一个声明,没有方法主体。下面是抽象方法声明时采用的语法:

abstract void X();

包含了抽象方法的一个类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法,类就必须指定成abstract(抽象)。否则,编译器会向我们报告一条出错消息。例:

abstract class Instrument4 {//因为包含一个abstract 的方法adjust,所以,类必须用abstract修饰

  int i; //storage allocated for each

  publicabstract void play();

  publicString what() {

    return"Instrument4";

  }

  publicabstract void adjust();

}

 

7.5 接口(interface)

“interface”(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。它允许创建者规定一个类的基本形式:方法名、自变量列表以及返回类型,但不规定方法主体。接口也包含了基本数据类型的数据成员,但它们都默认为static和final。

 

为创建一个接口,请使用interface关键字,而不要用class。

 

为了生成与一个特定的接口(或一组接口)相符的类,要使用implements(实现)关键字。

 

具体实现了一个接口以后,就获得了一个普通的类,可用标准方式对其进行扩展。例:

interface Instrument5 {    //注意用interface而不是用class

  //Compile-time constant:

  int i = 5;// static & final

  // Cannothave method definitions:

  voidplay(); // Automatically public

  Stringwhat();//不能有函数主体

  voidadjust();

}

 

class Wind5 implements Instrument5 {//用implements代替extends

  publicvoid play() {

   System.out.println("Wind5.play()");//定义函数主体

  }

  publicString what() { return "Wind5"; }

  publicvoid adjust() {}

}

 

class Percussion5 implements Instrument5 {

  publicvoid play() {

   System.out.println("Percussion5.play()");

  }

  publicString what() { return "Percussion5"; }

  publicvoid adjust() {}

}

class Brass5 extends Wind5 {//可以像一般类那样继承了

  publicvoid play() {

   System.out.println("Brass5.play()");

  }

  publicvoid adjust() {

   System.out.println("Brass5.adjust()");

  }

}

 

如何实现C++中的多重继承?

面这个例子展示了一个“具体”类同几个接口合并的情况,它最终生成了一个新类:

//: Adventure.java

// Multiple interfaces

import java.util.*;

 

interface CanFight {

  voidfight();

}

 

interface CanSwim {

  voidswim();

}

 

interface CanFly {

  voidfly();

}

 

class ActionCharacter {

  publicvoid fight() {}

}

 

class Hero extends ActionCharacter

   implements CanFight, CanSwim, CanFly {

  publicvoid swim() {}

  publicvoid fly() {}

}

 

注意上述例子已向我们揭示了接口最关键的作用,也是使用接口最重要的一个原因:能上溯造型至多个基础类。使用接口的第二个原因与使用抽象基础类的原因是一样的:防止客户程序员制作这个类的一个对象,以及规定它仅仅是一个接口。这样便带来了一个问题:到底应该使用一个接口还是一个抽象类呢?若使用接口,我们可以同时获得抽象类以及接口的好处。所以假如想创建的基础类没有任何方法定义或者成员变量,那么无论如何都愿意使用接口,而不要选择抽象类。事实上,如果事先知道某种东西会成为基础类,那么第一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类。

 

注意根据Java命名规则,拥有固定标识符的static final基本数据类型(亦即编译期常数)都全部采用大写字母(用下划线分隔单个标识符里的多个单词)。

 

7.5.4 初始化接口中的字段

接口中定义的字段会自动具有static和final属性。它们不能是“空白final”,但可初始化成非常数表达式。例如:

 

//: RandVals.java

// Initializing interface fields with

// non-constant initializers

import java.util.*;

 

public interface RandVals {

  int rint =(int)(Math.random() * 10);

  long rlong= (long)(Math.random() * 10);

  floatrfloat = (float)(Math.random() * 10);

  doublerdouble = Math.random() * 10;

} ///:~

 

 

7.6 嵌套类

将一个类的声明放到另一个类的里面.和C++中一样,非常有用,可以控制类的可见性.

和继承一样可以实现向上类型转换.

 

7.6.2 方法和作用域中的内部类

有两方面的原因促使我们这样做:

(1) 正如前面展示的那样,我们准备实现某种形式的接口,使自己能创建和返回一个句柄。

(2) 要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开。

 

②:这与C++“嵌套类”的设计颇有不同,后者只是一种单纯的名字隐藏机制。在C++中,没有指向一个封装对象的链接,也不存在默认的访问权限。

 

所以内部类不可拥有static数据或static内部类。

 

 

7.7.2 继承和finalize()

通过“合成”方法创建新类时,永远不必担心对那个类的成员对象的收尾工作。每个成员都是一个独立的对象,所以会得到正常的垃圾收集以及收尾处理--无论它是不是不自己某个类一个成员。但在进行初始化的时候,必须覆盖衍生类中的finalize()方法--如果已经设计了某个特殊的清除进程,要求它必须作为垃圾收集的一部分进行。覆盖衍生类的finalize()时,务必记住调用finalize()的基础类版本。否则,基础类的初始化根本不会发生。下面这个例子便是明证:

 

写一个例子证明在Java中是如何具体实现多态性的?(和C++中一样吗?)!!!!!!

例:

class Shape {

  voiddraw() {System.out.println("Shape.draw()"); }

  voiderase() {System.out.println("Shape.erase()");}

}

 

class Circle extends Shape {

  voiddraw() {

   System.out.println("Circle.draw()");

  }

  voiderase() {

   System.out.println("Circle.erase()");

  }

}

 

public class MyProject

{

   publicstatic void main(String[] args)

   {

      Shapes=new Circle();

      s.draw();

      s.erase();

   }

}

 

结果:

Circle.draw()

Circle.erase()

由此可以见,Java中的虚函数机制(多态)可以不依赖任何关键字就可以实现.这和C++中是不一样的(通过virtual实现)

另例:

class Shape {

  voiddraw() {System.out.println("Shape.draw()"); }

  voiderase() {System.out.println("Shape.erase()");}

}

 

class Circle extends Shape {

  voiddraw(int i) {

   System.out.println("Circle.draw()");

  }

  void erase() {

   System.out.println("Circle.erase()");

  }

}

 

public class MyProject

{

   publicstatic void main(String[] args)

   {

      Shapes=new Circle();

      s.draw();

      s.erase();

   }

}

结果:

Shape.draw()

Circle.erase()

 

另例:

class Shape {

  voiddraw() {System.out.println("Shape.draw()"); }

  voiderase() {System.out.println("Shape.erase()");}

}

 

class Circle extends Shape {

  int draw(){ //如果返回值类型和基类的draw不一样,编译会出现错误,不能通过.

   System.out.println("Circle.draw()");

  }

  voiderase() {

   System.out.println("Circle.erase()");

  }

}

 

public class MyProject

{

   publicstatic void main(String[] args)

   {

      Shapes=new Circle();

      s.draw();

      s.erase();

   }

}

 

 

 

第8章 对象的容纳

 

本章主要讲述容器类。对象的容纳是本章的重点,而数组只是容纳对象的一种方式。

矢量(Vector)的效率并没有数组高。

在Java中,无论使用的是数组还是集合,都会进行范围检查--若超过边界,就会获得一个RuntimeException(运行期违例)错误。在另一方面,由于C++的vector不进行范围检查,所以访问速度较快--在Java中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。

 

考虑到执行效率和类型检查,应尽可能地采用数组。

本章还要学习另外几种常见的集合类:Vector(矢量)、Stack(堆栈)以及Hashtable(散列表)。

 

集合类只能容纳对象句柄。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的句柄。所以基本数据类型不能放入Vector容器,这点不如C++.

 

8.1.2 数组的返回

Java采用的是类似的方法,但我们能“返回一个数组”。当然,此时返回的实际仍是指向数组的指针。但在Java里,我们永远不必担心那个数组的是否可用--只要需要,它就会自动存在。而且垃圾收集器会在我们完成后自动将其清除。这一点比在C++中简单。

 

当我们编写程序时,通常并不能确切地知道最终需要多少个对象。有些时候甚至想用更复杂的方式来保存对象。为解决这个问题,Java提供了四种类型的“集合类”:Vector(矢量)、BitSet(位集)、Stack(堆栈)以及Hashtable(散列表)。

 

8.2.1 缺点:类型未知

使用Java集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初编写集合时,那个集合的程序员根本不知道用户到底想把什么类型置入集合。为解决这个问题,集合实际容纳的是类型为Object的一些对象的句柄。这种类型当然代表Java中的所有对象,因为它是所有类的根。当然,也要注意这并不包括基本数据类型,因为它们并不是从“任何东西”继承来的。这是一个很好的方案,只是不适用下述场合:

(1) 将一个对象句柄置入集合时,由于类型信息会被抛弃,所以任何类型的对象都可进入我们的集合--即便特别指示它只能容纳特定类型的对象。举个例子来说,虽然指示它只能容纳猫,但事实上任何人都可以把一条狗扔进来。

(2) 由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。正式使用它之前,必须对其进行类型转换,使其具有正确的类型。

 

值得欣慰的是,Java不允许人们滥用置入集合的对象。假如将一条狗扔进一个猫的集合,那么仍会将集合内的所有东西都看作猫,所以在使用那条狗时会得到一个“违例”错误。在同样的意义上,假若试图将一条狗的句柄“造型”到一只猫,那么运行期间仍会得到一个“违例”错误。

 

至少,Java保留了关键字generic,期望有一天能够支持参数化类型。但我们现在无法确定这一天何时会来临。

 

Java中的迭代器除下面这些外,不可再用它做其他任何事情:

(1) 用一个名为elements()的方法要求集合为我们提供一个Enumeration。我们首次调用它的nextElement()时,这个Enumeration会返回序列中的第一个元素。

(2) 用nextElement()获得下一个对象。

(3) 用hasMoreElements()检查序列中是否还有更多的对象。

 

8.4.1 Vector

Vector的用法很简单,这已在前面的例子中得到了证明。尽管我们大多数时候只需用addElement()插入对象,用elementAt()一次提取一个对象,并用elements()获得对序列的一个“枚举”。但仍有其他一系列方法是非常有用的。

 

若确实想在这种情况下打印出对象的地址,解决方案就是调用Object的toString方法。此时就不必加入this,只需使用super.toString()。当然,采取这种做法也有一个前提:我们必须从Object直接继承,或者没有一个父类覆盖了toString方法。

 

8.4.2 BitSet

BitSet实际是由“二进制位”构成的一个Vector。如果希望高效率地保存大量“开-关”信息,就应使用BitSet。它只有从尺寸的角度看才有意义;如果希望的高效率的访问,那么它的速度会比使用一些固有类型的数组慢一些。

此外,BitSet的最小长度是一个长整数(Long)的长度:64位。这意味着假如我们准备保存比这更小的数据,如8位数据,那么BitSet就显得浪费了。所以最好创建自己的类,用它容纳自己的标志位。

 

8.4.3 Stack

Stack有时也可以称为“后入先出”(LIFO)集合。换言之,我们在堆栈里最后“压入”的东西将是以后第一个“弹出”的。和其他所有Java集合一样,我们压入和弹出的都是“对象”,所以必须对自己弹出的东西进行“类型转换”。

例:

Stack stk = new Stack();

    for(inti = 0; i < months.length; i++)

     stk.push(months[i] + " ");

 

while(!stk.empty())

     System.out.println(stk.pop());

 

要声明的一点是,Vector操作亦可针对Stack对象进行。这可能是由继承的特质决定的--Stack“属于”一种Vector。因此,能对Vector进行的操作亦可针对Stack进行,例如elementAt()方法。

 

8.4.4 Hashtable

在Java中,这个概念具体反映到抽象类Dictionary身上。该类的接口是非常直观的size()告诉我们其中包含了多少元素;isEmpty()判断是否包含了元素(是则为true);put(Objectkey, Object value)添加一个值(我们希望的东西),并将其同一个键关联起来(想用于搜索它的东西);get(Object key)获得与某个键对应的值;而remove(ObjectKey)用于从列表中删除“键-值”对。还可以使用枚举技术:keys()产生对键的一个枚举(Enumeration);而elements()产生对所有值的一个枚举。这便是一个Dictionary(字典)的全部。

 

例:

 

8.5 排序

 

通过本章的学习,大家已知道标准Java库提供了一些特别有用的集合,但距完整意义的集合尚远。除此之外,象排序这样的算法根本没有提供支持。

现在仍然没有支持吗?

 

 

8.7.7 排序和搜索

Java 1.2添加了自己的一套实用工具,可用来对数组或列表进行排列和搜索。这些工具都属于两个新类的“静态”方法。

 

1. 数组

Arrays类为所有基本数据类型的数组提供了一个重载的sort()和binarySearch(),它们亦可用于String和Object。

2. 可比较与比较器

 

3. 列表

可用与数组相同的形式排序和搜索一个列表(List)。

 

 

第9章 违例差错控制(和C++几乎一样)

 

9.2.4 捕获所有违例

我们可创建一个控制器,令其捕获所有类型的违例。具体的做法是捕获基础类违例类型Exception(也存在其他类型的基础违例,但Exception是适用于几乎所有编程活动的基础)。如下所示:

catch(Exception e) {

System.out.println("caught anexception");

}

 

例:

public class ExceptionMethods {

  publicstatic void main(String[] args) {

    try {

      thrownew Exception("Here's my Exception");

    }catch(Exception e) {

     System.out.println("Caught Exception");

     System.out.println(

       "e.getMessage(): " + e.getMessage());

     System.out.println(

       "e.toString(): " + e.toString());

     System.out.println("e.printStackTrace():");

     e.printStackTrace();

    }

  }

} ///:~

 

9.3.1 RuntimeException的特殊情况

本章的第一个例子是:

if(t == null)

throw new NullPointerException();

 

若对一个空句柄发出了调用,Java会自动产生一个NullPointerException违例。所以上述代码在任何情况下都是多余的。

 

9.4 创建自己的违例

class MyException extends Exception {

  publicMyException() {}

  publicMyException(String msg) {

   super(msg);

  }

}

 

9.6 用finally清除

无论一个违例是否在try块中发生,我们经常都想执行一些特定的代码。对一些特定的操作,经常都会遇到这种情况,但在恢复内存时一般都不需要(因为垃圾收集器会自动照料一切)。为达到这个目的,可在所有违例控制器的末尾使用一个finally从句(注释④)。所以完整的违例控制小节象下面这个样子:

 

try {

// 要保卫的区域:

// 可能“掷”出A,B,或C的危险情况

} catch (A a1) {

// 控制器 A

} catch (B b1) {

// 控制器 B

} catch (C c1) {

// 控制器 C

} finally {

// 每次都会发生的情况

}

 

 

第10章 Java IO系统

 

“对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。”

 

由于存在大量不同的设计方案,所以该任务是困难的。其中最大的挑战似乎是如何覆盖所有可能的因素。不仅有三种不同种类的IO需要考虑(文件、控制台、网络连接),而且需要通过大量不同的方式与它们通信(顺序、随机访问、二进制、字符、按行、按字等等)。

Java库的设计者通过创建大量类来攻克这个难题。

 

例://列出某个目录下的所有文件和目录

import java.io.*;

 

public class My {

  publicstatic void main(String[] args) {

       Filepath = new File(".."); //".."表示当前目录的上一级目录、"."表示当前目录、"d:\\"

     String[] list = path.list();

      for(int i = 0; i < list.length; i++)

       System.out.println(list[i]);

  }

 

}

 

 

 

第11章 运行期类型鉴定

 

public static void main(String[] args) {

    Vector s= new Vector();

   s.addElement(new Circle());

   s.addElement(new Square());

   s.addElement(new Triangle());

   Enumeration e = s.elements();

   while(e.hasMoreElements())

     ((Shape)e.nextElement()).draw();

  }

 

11.1.1 Class对象

为理解RTTI在Java里如何工作,首先必须了解类型信息在运行期是如何表示的。这时要用到一个名为“Class对象”的特殊形式的对象,其中包含了与类有关的信息。事实上,我们要用Class对象创建属于某个类的全部“常规”或“普通”对象。

每个类都有一个Class对象。换言之,每次创建一个新类时,同时也会创建一个Class对象(更恰当地说,是保存在一个完全同名的.class文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的Java虚拟机(JVM)首先就会检查那个类型的Class对象是否已经载入。若尚未载入,JVM就会查找同名的.class文件,并将其载入。所以Java程序启动时并不是完全载入的,这一点与许多传统语言都不同。

一旦那个类型的Class对象进入内存,就用它创建那一类型的所有对象。

 

class Candy {

  static {

   System.out.println("Loading Candy");

  }

}

public class My {

  publicstatic void main(String[] args) {

   try{

  Class.forName("Candy");   //检查Gum的Class对象是否被加载进内存,如果没有就加载它,加载GumGum时static块会被执行(跟踪)

 }catch(ClassNotFoundException e){

  e.printStackTrace();

  }

  }

} ///:~

 

Java的装载模块其实就是用Class对象完成类装载的。

 

1. 类标记

在Java 1.1中,可以采用第二种方式来产生Class对象的句柄:使用“类标记”。对上述程序来说,看起来就象下面这样:

Gum.class;

这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。

类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。

 

2.如何判断一个类是不是另一个类的子类?

例:

class Base{

  

}

class C extends Base{

  

}

public class My{

   publicstatic void main(String[] args){

      C c=newC();

      if(cinstanceof Base) //关键字instanceof前面的参数为实例句柄,后面的参数为类名

         System.out.println("is");

      else

         System.out.println("no");

   }

}

 

3.另一种产生实例的方法:

class Base{

   Base(){

      System.out.println("Createinstance");

   }

}

 

public class My{

   publicstatic void main(String[] args){

         try {

            Base.class.newInstance();

          } catch(InstantiationException e) {}

            catch(IllegalAccessException e) {}

   }

}

 

Java 1.1的isInstance()方法已取消了对instanceof表达式的需要。

例:

Base c=new C();

   if(Base.class.isInstance(c))

      System.out.println("is");

 

如果已有了它的一个对象,那么为了取得Class句柄,可调用属于Object根类一部分的一个方法:getClass()。它的作用是返回一个特定的Class句柄,用来表示对象的实际类型。

 

所以利用Class对象,我们几乎能将一个对象的祖宗十八代都调查出来。

 

11.3 反射:运行期类信息(重点)

如果不知道一个对象的准确类型,RTTI会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI要处理的所有类。

从表面看,这似乎并不是一个很大的限制,但假若得到的是一个不在自己程序空间内的对象的句柄,这时又会怎样呢?事实上,对象的类即使在编译期间也不可由我们的程序使用。例如,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?

在传统的程序设计环境中,出现这种情况的概率或许很小。但当我们转移到一个规模更大的编程世界中,却必须对这个问题加以高度重视。第一个要注意的是基于组件的程序设计。在这种环境下,我们用“快速应用开发”(RAD)模型来构建程序项目。RAD一般是在应用程序构建工具中内建的。这是编制程序的一种可视途径(在屏幕上以窗体的形式出现)。可将代表不同组件的图标拖曳到窗体中。随后,通过设定这些组件的属性或者值,进行正确的配置。设计期间的配置要求任何组件都是可以“例示”的(即可以自由获得它们的实例)。这些组件也要揭示出自己的一部分内容,允许程序员读取和设置各种值。此外,用于控制GUI事件的组件必须揭示出与相应的方法有关的信息,以便RAD环境帮助程序员用自己的代码覆盖这些由事件驱动的方法。“反射”提供了一种特殊的机制,可以侦测可用的方法,并产生方法名。通过JavaBeans(第13章将详细介绍),Java 1.1为这种基于组件的程序设计提供了一个基础结构。

在运行期查询类信息的另一个原动力是通过网络创建与执行位于远程系统上的对象。这就叫作“远程方法调用”(RMI),它允许Java程序(版本1.1以上)使用由多台机器发布或分布的对象。这种对象的分布可能是由多方面的原因引起的:可能要做一件计算密集型的工作,想对它进行分割,让处于空闲状态的其他机器分担部分工作,从而加快处理进度。某些情况下,可能需要将用于控制特定类型任务(比如多层客户/服务器架构中的“运作规则”)的代码放置在一台特殊的机器上,使这台机器成为对那些行动进行描述的一个通用储藏所。而且可以方便地修改这个场所,使其对系统内的所有方面产生影响(这是一种特别有用的设计思路,因为机器是独立存在的,所以能轻易修改软件!)。分布式计算也能更充分地发挥某些专用硬件的作用,它们特别擅长执行一些特定的任务--例如矩阵逆转--但对常规编程来说却显得太夸张或者太昂贵了。

在Java 1.1中,Class类(本章前面已有详细论述)得到了扩展,可以支持“反射”的概念。针对Field,Method以及Constructor类(每个都实现了Memberinterface--成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field对象关联的字段,以及用invoke()方法调用与Method对象关联的方法。此外,我们可调用方法getFields(),getMethods(),getConstructors(),分别返回用于表示字段、方法以及构建器的对象数组(在联机文档中,还可找到与Class类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。

大家要认识的很重要的一点是“反射”并没有什么神奇的地方。通过“反射”同一个未知类型的对象打交道时,JVM只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的RTTI那样)。但在这之后,在我们做其他任何事情之前,Class对象必须载入。因此,用于那种特定类型的.class文件必须能由JVM调用(要么在本地机器内,要么可以通过网络取得)。所以RTTI和“反射”之间唯一的区别就是对RTTI来说,编译器会在编译期打开和检查.class文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class文件在编译期间是不可使用的,而是由运行期环境打开和检查。

 

11.3.1 一个类方法提取器

很少需要直接使用反射工具;之所以在语言中提供它们,仅仅是为了支持其他Java特性,比如对象序列化(第10章介绍)、JavaBeans以及RMI(本章后面介绍)。但是,我们许多时候仍然需要动态提取与一个类有关的资料。其中特别有用的工具便是一个类方法提取器。正如前面指出的那样,若检视类定义源码或者联机文档,只能看到在那个类定义中被定义或覆盖的方法,基础类那里还有大量资料拿不到。幸运的是,“反射”做到了这一点,可用它写一个简单的工具,令其自动展示整个接口。下面便是具体的程序:

 

//: ShowMethods.java

// Using Java 1.1 reflection to show all the

// methods of a class, even if the methods are

// defined in the base class.

import java.lang.reflect.*;

 

public class ShowMethods {

  staticfinal String usage =

   "usage: \n" +

   "ShowMethods qualified.class.name\n" +

    "Toshow all methods in class or: \n" +

   "ShowMethods qualified.class.name word\n" +

    "Tosearch for methods involving 'word'";

  publicstatic void main(String[] args) {

   if(args.length < 1) {

     System.out.println(usage);

     System.exit(0);

    }

    try {

      Classc = Class.forName(args[0]);

     Method[] m = c.getMethods();

     Constructor[] ctor = c.getConstructors();

     if(args.length == 1) {

        for(int i = 0; i < m.length; i++)

         System.out.println(m[i].toString());

        for(int i = 0; i < ctor.length; i++)

          System.out.println(ctor[i].toString());

      }

      else {

        for(int i = 0; i < m.length; i++)

         if(m[i].toString()

            .indexOf(args[1])!= -1)

           System.out.println(m[i].toString());

        for(int i = 0; i < ctor.length; i++)

         if(ctor[i].toString()

            .indexOf(args[1])!= -1)

         System.out.println(ctor[i].toString());

      }

    } catch(ClassNotFoundException e) {

     System.out.println("No such class: " + e);

    }

  }

} ///:~

 

Class方法getMethods()和getConstructors()可以分别返回Method和Constructor的一个数组。每个类都提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签名是否与我们的目标字串相符(使用indexOf()),并打印出结果。

这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也属于几乎完全不用我们操心的一个步骤--Java自己会利用这种支持,所以程序设计环境能够控制JavaBeans--但它无论如何都是非常有趣的。

一个有趣的试验是运行java ShowMehods ShowMethods。这样做可得到一个列表,其中包括一个public默认构建器,尽管我们在代码中看见并没有定义一个构建器。我们看到的是由编译器自动合成的那一个构建器。如果随之将ShowMethods设为一个非public类(即换成“友好”类),合成的默认构建器便不会在输出结果中出现。合成的默认构建器会自动获得与类一样的访问权限。

ShowMethods的输出仍然有些“不爽”。例如,下面是通过调用javaShowMethods java.lang.String得到的输出结果的一部分:

 

public boolean

 java.lang.String.startsWith(java.lang.String,int)

public boolean

 java.lang.String.startsWith(java.lang.String)

public boolean

 java.lang.String.endsWith(java.lang.String)

 

假如记不得一个类是否有一个特定的方法,而且不想在联机文档里逐步检查类结构,或者不知道那个类是否能对某个对象(如Color对象)做某件事情,该工具便可节省大量编程时间。

第17章提供了这个程序的一个GUI版本,可在自己写代码的时候运行它,以便快速查找需要的东西。

 

第12章 传递和返回对象

记住,实际传递的只是一个句柄。

  PassHandles p = new PassHandles();

  System.out.println("p inside main(): " + p);     //打印出的是p的地址

12.2.2 克隆对象

若需修改一个对象,同时不想改变调用者的对象,就要制作该对象的一个本地副本。这也是本地副本最常见的一种用途。若决定制作一个本地副本,只需简单地使用clone()方法即可。

import java.util.*;

 

class Int {

  private int i;

  publicInt(int ii) { i = ii; }

  publicvoid increment() { i++; }

  publicString toString() {

    returnInteger.toString(i); //整数i将被转换成String

  }

}

 

public class Cloning {

  publicstatic void main(String[] args) {

    Vector v= new Vector();

    for(inti = 0; i < 10; i++ )

     v.addElement(new Int(i));

   System.out.println("v: " + v);//这种做法在C++中少见

    Vectorv2 = (Vector)v.clone();//现在v2和v指向的就不是同一个Vector对象了,v2指向一个Vector对象的副本

    //Increment all v2's elements:

   for(Enumeration e = v2.elements();

       e.hasMoreElements(); )

     ((Int)e.nextElement()).increment();//会影响v,因为是“简单复制”

    // Seeif it changed v's elements:

   System.out.println("v: " + v);

  }

} ///:~

 

以下将如何进行深层克隆:

总之,克隆时要注意的两个关键问题是:几乎肯定要调用super.clone(),以及注意将克隆设为public。

12.2.4 成功的克隆

import java.util.*;

 

class MyObject implements Cloneable {

  int i;

 MyObject(int ii) { i = ii; }

  publicObject clone() {

    Object o= null;

    try {

      o =super.clone();   //根类中的clone()方法负责建立正确的存储容量,并通过“按位复制”将二进制位从原始对象中复制到新对象的存储空间。这个过程需要用RTTI判断欲克隆的对象的实际大小。为了深层克隆,必须由自己明确进行。(道理和C++中的拷贝构造函数和=)

    } catch(CloneNotSupportedException e) {

     System.out.println("MyObject can't clone");

    }

    returno;

  }

  publicString toString() {

    returnInteger.toString(i);

  }

}

 

public class LocalCopy {

  staticMyObject g(MyObject v) {

    //Passing a handle, modifies outside object:

    v.i++;

    returnv;

  }

  staticMyObject f(MyObject v) {

    v =(MyObject)v.clone(); // Local copy

    v.i++;

    returnv;

  }

  publicstatic void main(String[] args) {

    MyObjecta = new MyObject(11);

    MyObjectb = g(a);

    //Testing handle equivalence,

    // notobject equivalence:

    if(a ==b)

     System.out.println("a == b");

    else

     System.out.println("a != b");

   System.out.println("a = " + a);

   System.out.println("b = " + b);

    MyObjectc = new MyObject(47);

    MyObjectd = f(c);

    if(c ==d)

     System.out.println("c == d");

    else

     System.out.println("c != d");

   System.out.println("c = " + c);

   System.out.println("d = " + d);

  }

} ///:~

 

12.2.7 用Vector进行深层复制

下面让我们复习一下本章早些时候提出的Vector例子。这一次Int2类是可以克隆的,所以能对Vector进行深层复制:

 

//: AddingClone.java

// You must go through a few gyrations to

// add cloning to your own class.

import java.util.*;

 

class Int2 implements Cloneable {

  privateint i;

  publicInt2(int ii) { i = ii; }

  publicvoid increment() { i++; }

  publicString toString() {

    returnInteger.toString(i);

  }

  publicObject clone() {

    Object o= null;

    try {

      o =super.clone();

    } catch(CloneNotSupportedException e) {

     System.out.println("Int2 can't clone");

    }

    returno;

  }

}

 

// Once it's cloneable, inheritance

// doesn't remove cloneability:

class Int3 extends Int2 {

  privateint j; // Automatically duplicated

  publicInt3(int i) { super(i); }

}

 

public class AddingClone {

  publicstatic void main(String[] args) {

    Int2 x =new Int2(10);

    Int2 x2= (Int2)x.clone();

   x2.increment();

   System.out.println(

     "x = " + x + ", x2 = " + x2);

    //Anything inherited is also cloneable:

    Int3 x3= new Int3(7);

    x3 =(Int3)x3.clone();

 

    Vector v= new Vector();

    for(inti = 0; i < 10; i++ )

     v.addElement(new Int2(i));

   System.out.println("v: " + v);

    Vectorv2 = (Vector)v.clone();

    // Nowclone each element:

    for(inti = 0; i < v.size(); i++)

     v2.setElementAt(

       ((Int2)v2.elementAt(i)).clone(), i);//在克隆了Vector后,必须在其中遍历,并克隆由Vector指向的每个对象。

    // Incrementall v2's elements:

   for(Enumeration e = v2.elements();

       e.hasMoreElements(); )

     ((Int2)e.nextElement()).increment();

    // Seeif it changed v's elements:

   System.out.println("v: " + v);

   System.out.println("v2: " + v2);

  }

} ///:~

 

Int3自Int2继承而来,并添加了一个新的基本类型成员int j。大家也许认为自己需要再次覆盖clone(),以确保j得到复制,但实情并非如此。将Int2的clone()当作Int3的clone()调用时,它会调用Object.clone(),判断出当前操作的是Int3,并复制Int3内的所有二进制位。只要没有新增需要克隆的句柄,对Object.clone()的一个调用就能完成所有必要的复制--无论clone()是在层次结构多深的一级定义的。

至此,大家可以总结出对Vector进行深层复制的先决条件:在克隆了Vector后,必须在其中遍历,并克隆由Vector指向的每个对象。为了对Hashtable(散列表)进行深层复制,也必须采取类似的处理。

 

12.2.8 通过序列化进行深层复制

若研究一下第10章介绍的那个Java 1.1对象序列化示例,可能发现若在一个对象序列化以后再撤消对它的序列化,或者说进行装配,那么实际经历的正是一个“克隆”的过程。

那么为什么不用序列化进行深层复制呢?下面这个例子通过计算执行时间对比了这两种方法:

 

//: Compete.java

import java.io.*;

 

class Thing1 implements Serializable {}

class Thing2 implements Serializable {

  Thing1 o1= new Thing1();

}

 

class Thing3 implements Cloneable {

  publicObject clone() {

    Object o= null;

    try {

      o =super.clone();

    } catch(CloneNotSupportedException e) {

     System.out.println("Thing3 can't clone");

    }

    returno;

  }

}

 

class Thing4 implements Cloneable {

  Thing3 o3= new Thing3();

  publicObject clone() {

    Thing4 o= null;

    try {

      o =(Thing4)super.clone();

    } catch(CloneNotSupportedException e) {

     System.out.println("Thing4 can't clone");

    }

    // Clonethe field, too:

    o.o3 =(Thing3)o3.clone();

    returno;

  }

}

 

public class Compete {

  staticfinal int SIZE = 5000;

  publicstatic void main(String[] args) {

    Thing2[]a = new Thing2[SIZE];

    for(inti = 0; i < a.length; i++)

      a[i] =new Thing2();

    Thing4[]b = new Thing4[SIZE];

    for(inti = 0; i < b.length; i++)

      b[i] =new Thing4();

    try {

      longt1 = System.currentTimeMillis();

     ByteArrayOutputStream buf =

        newByteArrayOutputStream();

     ObjectOutputStream o =

        newObjectOutputStream(buf);

     for(int i = 0; i < a.length; i++)

       o.writeObject(a[i]);

      // Nowget copies:

     ObjectInputStream in =

        newObjectInputStream(

         new ByteArrayInputStream(

           buf.toByteArray()));

     Thing2[] c = new Thing2[SIZE];

     for(int i = 0; i < c.length; i++)

        c[i]= (Thing2)in.readObject();

      longt2 = System.currentTimeMillis();

     System.out.println(

       "Duplication via serialization: " +

        (t2- t1) + " Milliseconds");

      // Nowtry cloning:

      t1 =System.currentTimeMillis();

     Thing4[] d = new Thing4[SIZE];

     for(int i = 0; i < d.length; i++)

        d[i]= (Thing4)b[i].clone();

      t2 =System.currentTimeMillis();

     System.out.println(

       "Duplication via cloning: " +

        (t2- t1) + " Milliseconds");

    }catch(Exception e) {

     e.printStackTrace();

    }

  }

} ///:~

 

除了序列化和克隆之间巨大的时间差异以外,我们也注意到序列化技术的运行结果并不稳定,而克隆每一次花费的时间都是相同的。

 

12.3 克隆的控制

为消除克隆能力,大家也许认为只需将clone()方法简单地设为private(私有)即可,但这样是行不通的,因为不能采用一个基础类方法,并使其在衍生类中更“私有”。所以事情并没有这么简单。此外,我们有必要控制一个对象是否能够克隆。对于我们设计的一个类,实际有许多种方案都是可以采取的:

(1) 保持中立,不为克隆做任何事情。

(2) 支持clone(),采用实现Cloneable(可克隆)能力的标准操作,并覆盖clone()。在被覆盖的clone()中,可调用super.clone(),并捕获所有违例(这样可使clone()不“掷”出任何违例)。

(3) 有条件地支持克隆。

(4) 不实现Cloneable(),但是将clone()覆盖成protected,使任何字段都具有正确的复制行为。

(5) 不实现Cloneable来试着防止克隆,并覆盖clone(),以产生一个违例。

(6) 将类设为final,从而防止克隆。

下面这个例子总结了克隆的各种实现方法,然后在层次结构中将其“关闭”:

 

//: CheckCloneable.java

// Checking to see if a handle can be cloned

 

// Can't clone this because it doesn't

// override clone():

class Ordinary {}

 

// Overrides clone, but doesn't implement

// Cloneable:

class WrongClone extends Ordinary {

  publicObject clone()

      throwsCloneNotSupportedException {

    returnsuper.clone(); // Throws exception

  }

}

 

// Does all the right things for cloning:

class IsCloneable extends Ordinary

   implements Cloneable {

  publicObject clone()

      throwsCloneNotSupportedException {

    returnsuper.clone();

  }

}

 

// Turn off cloning by throwing the exception:

class NoMore extends IsCloneable {

  publicObject clone()

      throwsCloneNotSupportedException {

    thrownew CloneNotSupportedException();

  }

}

 

class TryMore extends NoMore {

  publicObject clone()

      throwsCloneNotSupportedException {

    // CallsNoMore.clone(), throws exception:

    returnsuper.clone();

  }

}

 

class BackOn extends NoMore {

  privateBackOn duplicate(BackOn b) {

    //Somehow make a copy of b

    // andreturn that copy. This is a dummy

    // copy,just to make the point:

    returnnew BackOn();

  }

  publicObject clone() {

    //Doesn't call NoMore.clone():

    returnduplicate(this);

  }

}

 

// Can't inherit from this, so can't override

// the clone method like in BackOn:

final class ReallyNoMore extends NoMore {}

 

public class CheckCloneable {

  staticOrdinary tryToClone(Ordinary ord) {

    Stringid = ord.getClass().getName();

    Ordinaryx = null;

    if(ordinstanceof Cloneable) {

      try {

       System.out.println("Attempting " + id);

        x =(Ordinary)((IsCloneable)ord).clone();

       System.out.println("Cloned " + id);

      }catch(CloneNotSupportedException e) {

       System.out.println(

         "Could not clone " + id);

      }

    }

    returnx;

  }

  publicstatic void main(String[] args) {

    //Upcasting:

   Ordinary[] ord = {

      newIsCloneable(),

      newWrongClone(),

      newNoMore(),

      newTryMore(),

      newBackOn(),

      newReallyNoMore(),

    };

    Ordinaryx = new Ordinary();

    // Thiswon't compile, since clone() is

    //protected in Object:

    //! x =(Ordinary)x.clone();

    //tryToClone() checks first to see if

    // aclass implements Cloneable:

    for(inti = 0; i < ord.length; i++)

     tryToClone(ord[i]);

  }

} ///:~

 

总之,如果希望一个类能够克隆,那么:

(1) 实现Cloneable接口

(2) 覆盖clone()

(3) 在自己的clone()中调用super.clone()

(4) 在自己的clone()中捕获违例

这一系列步骤能达到最理想的效果。

 

12.3.1 拷贝构造函数

克隆看起来要求进行非常复杂的设置,似乎还该有另一种替代方案。一个办法是制作特殊的构建器,令其负责复制一个对象。

 

所以非常不幸,假如想制作对象的一个本地拷贝,Java中的拷贝构造函数便不是特别适合我们。

 

12.4 只读类

作为“不变对象”一个简单例子,Java的标准库包含了“封装器”(wrapper)类,可用于所有基本数据类型。

 

Integer类(以及基本的“封装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方法。

 

若确实需要一个容纳了基本数据类型的对象,并想对基本数据类型进行修改,就必须亲自创建它们。幸运的是,操作非常简单:

 

//: MutableInteger.java

// A changeable wrapper class

import java.util.*;

 

class IntValue {

  int n;//通过改n就可以达到目的了,如果是Integer则没有办法改变它的对象

 IntValue(int x) { n = x; }

  public String toString() {

    returnInteger.toString(n);

  }

}

 

12.4.1 创建只读类

只要将所有的数据都设置为私有,并且不提供修改类数据的任何方法就可以了。

 

12.4.3 不变字符串

请观察下述代码:

 

//: Stringer.java

 

public class Stringer {

  staticString upcase(String s) {

    returns.toUpperCase();//按理说三s会受到影响,实际上不会。

  }

  publicstatic void main(String[] args) {

    String q= new String("howdy");

   System.out.println(q); // howdy

    Stringqq = upcase(q);

   System.out.println(qq); // HOWDY

   System.out.println(q); // howdy

  }

} ///

 

 

//

 

第3章

在C++中,需要使用sizeof()的最大原因是为了“移植”。不同数据类型在不同机器上可能有不同的大小,所以在进行一些与存储空间有关的运算时,程序员必须清楚那些类型具体有多大。Java不需要sizeof()操作符是因为所有数据类型在所有机器中的大小都是相同的,我们不必考虑移植问题-它已经设计在语言中了。

 

第4章

Java并未提供析构函数或相似的概念,要做类似的清理工作,必须自己动手创建一个执行清理公子做的普通方法。

垃圾回收只与内存有关。

4.3.4 垃圾回收器如何工作(重点)

垃圾回收器的工作方式,使Java从堆分配空间的速度,可以和其他语言从栈上分配空间的速度相媲美。(类似于vector的工作方式,平时仅仅是扩展,间隙性地整理内存以便更紧凑)

 

“引用计数”是一种简单但速度很慢的垃圾回收技术,引用技术常用来说明垃圾回收机制的工作方式,但似乎从未被应用于任何一种Java虚拟机实现中(可能出现交叉引用缺陷)。

 

在一些更快的模式中,垃圾回收器是根据所有的引用去反查哪些对象是活动的(没明白如何解决交叉引用?)。

 

在一些更快......自动回收了。

 

停止-复制

标记-清除

 

Java虚拟机有许多附加技术用以提升速度,比如JIT,惰性评估。

 

静态初始化(静态变量或静态块)只有在必要的时候(对象产生或静态变量被访问)才会进行。

 

第5章 隐藏具体实现

一种合乎逻辑的做法是将特定包的所有.class文件都置于一个目录下。包名一定要按反序Internet域名来。注意,包和包之间都是平行关系,虽然在物理目录上可能有包含关系,从eclipse的包浏览器上就可以看出来。所有的包都是在同一层。

务必记住,无论何时创建包,都已经在给定包的名称的时候隐含地指定了目录结构。

 

第6章

 

第7章 多态

Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。

 

建立这个通用接口的唯一理由是,不同的子类可以用不同的方式表示接口。

是不是意味着一般包含多个子类的情况才需要搞一个接口类出来?对于ITestExecutor没把我烦死!

答:参考一下JDK或别人的代码

 

第8章 接口与内部类

内部类除了是一种名字隐藏和组织代码的模式(将实现隐藏,通过工厂方法向外提供接口),还能和制造它的外围对象之间实现回调,因为它能访问其外围对象的所有成员,而不需要任何特殊条件。(以前在C++中经常遇到这种情况,结果需要将外围类的this传进去,且需要将需要回调的函数搞成public,而在Java中编译器帮我们做了)

 

对于匿名类同样可以。

 

第9章 通过异常处理错误

在C中,程序员有时不去检查错误,而在Java中,用强制规定的形式来消除错误处理过程中随心所欲的因素。

使用异常所带来的另一个相当明显的好处是,它能使错误代码变得更有条理,与原先对于同一个错误,要在多个地方进行检查和处理相比,你不必在方法调用处进行检查。(因为异常捕获机制能捕获这个错误),并且,只需要在一个地方处理错误,即所谓的“异常处理程序”中。这种方式不仅节省代码,并且把描述做什么事的代码和出了问题怎么办的代码相分离,总之,与以前的错误处理方式相比,异常机制使代码的阅读,编写和调试工作更加井井有条。

注意在try块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只xuyao 提供一个针对此类型的异常处理程序。

 

对于if(t == null){

       throw new NullPointerException();

如果必须对传递给方法的每个引用都检查是否为null,这听起来确实吓人。幸运的是,你不必由你亲自来做,它属于Java的标准运行检查的一部分。如果对null引用进行调用,Java会自动抛出Null异常,所以上述代码是多余的。

属于运行时异常的类型很多,它们会被Java虚拟机自动列出来,所以不必在异常说明中将它们列出来。对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行,为了达到这个效果,可以在异常处理程序后面加上finally子句。

当要把内存之外的资源恢复到它们的初始化状态时,就要用到finally子句,这种需要清理的资源包括:已经打开的文件或网络连接等。

......因此,如果真的要用finally进行清理的话,可以在构造函数正常结束时设置某种标志,如果设定这个标志,就不必在finally块中做任何清理,这种方法不是特别优雅,因此除非没有别的办法。最好还是避免像这样在finally里进行清理。

异常处理的一个重要原则是:“只有在你知道如何处理的情况下才捕获异常。”事实上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。

9.12 异常使用指南

应该在下列情况下使用异常:

1)

9)

 

第10章 类型检查

反射:运行时的类信息

人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力。

重要的是,要认识到反射机制并没有什么神奇之处(有点像DLL的动态加载)。RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。换句话说,我们可以用“普通”方式调用对象的所有方法。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

很少直接使用反射工具,它们在Java中是用来支持其他特性的,例如第12章的对象序列化,第14章的JavaBeans。

 

第11章 对象的集合

8)新程序中不应该使用过时的Vector,Hashtable和Stack。

 

第12章 Java I/O系统

略,无非就是一些常用类使用

 

第13章 并发

 

第14章 创建窗口与applet程序

 

第15章 发现问题

单元测试JUnit

断言

用ANT构建

用CVS进行版本控制

日志Logger

调试JDB

剖析和优化(profiler)

doclet

 

第16章 分析与设计

在整个开发过程中,最重要的一点是:不要不知所措。这很容易做到。大多数分析与设计方法论都试图解决最大型的问题。但是请记住,多数工程并不属于这一范畴。我们通常只需要采用某种方法论锁推荐的一个相对较小的子集就可以得到成功的分析和设计。但是某些类型的处理过程,无论它们怎样小或者怎样受限制,通常也都应该采用某种比较直接开始编码要好得多的方式。

这很容易使人陷入“分析瘫痪”的困境,使你感觉无法取得进展,因为你无法确定当前阶段的每个细节。记住,无论你做了多少分析,总有些问题不到设计阶段是无法发现的,而更多的问题直到编码阶段或者程序运行时才会暴露出来。因此,快速完成分析与设计阶段,并且开发的系统进行测试才是至关重要的。

 

参考

<<Java Network Programming>>口碑不错,主要讲一些输入输出类,没什么特别之处,暂时不细看。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值