第9章 违例差错控制 Java的基本原理就是“形式错误的代码不会运行”。 <1> 在Java中,对那些要调用方法的客户程序员,我们要通知他们可能从自己的方法里“掷”出违例。这是一种有礼貌的做法,只有它 才能使客户程序员准确地知道要编写什么代码来捕获所有潜在的违例。当然,若你同时提供了源码,客户程序员甚至能全盘检查 代码,找出相应的throw语句。但尽管如此,通常并不随同源码提供库。为解决这个问题,Java提供了一种特殊的语法格式(并强 迫我们采用),以便礼貌地告诉客户程序员该方法会“掷”出什么违例,令对方方便地加以控制。这便是我们在这里要讲述的“违例 规范”,它属于方法声明的一部分,位于自变量(参数)列表的后面。 违例规范采用了一个额外的关键字:throws;后面跟随全部潜在的违例类型。因此,我们的方法定义看起来应象下面这个样子: void f() throws tooBig, tooSmall, divZero { //... } <2>捕获所有违例 我们可创建一个控制器,令其捕获所有类型的违例。具体的做法是捕获基础类违例类型Exception(也存在其他类型的基础违例, 但Exception是适用于几乎所有编程活动的基础)。如下所示: catch(Exception e) { System.out.println("caught an exception"); } 这段代码能捕获任何违例,所以在实际使用时最好将其置于控制器列表的末尾,防止跟随在后面的任何特殊违例控制器失效。 对于程序员常用的所有违例类来说,由于Exception类是它们的基础,所以我们不会获得关于违例太多的信息,但可调用来自它 的基础类Throwable的方法: String getMessage() 获得详细的消息。 String toString() 返回对Throwable的一段简要说明,其中包括详细的消息(如果有的话)。 <3>标准Java违例 Java包含了一个名为Throwable的类,它对可以作为违例“掷”出的所有东西进行了描述。Throwable对象有两种常规类型(亦即 “从Throwable继承”)。其中,Error代表编译期和系统错误,我们一般不必特意捕获它们(除在特殊情况以外)。Exception 是可以从任何标准Java库的类方法中“掷”出的基本类型。此外,它们亦可从我们自己的方法以及运行期偶发事件中“掷”出。 <4>违例准则 用违例做下面这些事情 : (1) 解决问题并再次调用造成违例的方法。 (2) 平息事态的发展,并在不重新尝试方法的前提下继续。 (3) 计算另一些结果,而不是希望方法产生的结果。 (4) 在当前环境中尽可能解决问题,以及将相同的违例重新“掷”出一个更高级的环境。 (5) 在当前环境中尽可能解决问题,以及将不同的违例重新“掷”出一个更高级的环境。 (6) 中止程序执行。 (7) 简化编码。若违例方案使事情变得更加复杂,那就会令人非常烦恼,不如不用。 (8) 使自己的库和程序变得更加安全。这既是一种“短期投资”(便于调试),也是一种“长期投资”(改善应用程序的健壮性) 第10章 Java IO系统
“对语言设计人员来说,创建好的输入、输出系统是一项特别困难的任务。” 由于存在大量不同的设计方案,所以该任务的困难性是很容易证明的。其中最大的挑战似乎是如何覆盖所有可能的因素。不仅有 三种不同的种类的IO需要考虑(文件、控制台、网络连接),而且需要通过大量不同的方式与它们通信(顺序、随机访问、二进 制、字符、按行、按字等等)。 可将Java库的IO类分割为输入与输出两个部分。通过继承,从InputStream(输入流)衍生的所有类都拥有名为read()的基本 方法,用于读取单个字节或者字节数组。类似地,从OutputStream衍生的所有类都拥有基本方法write(),用于写入单个字节 或者字节数组。然而,我们通常不会用到这些方法;它们之所以存在,是因为更复杂的类可以利用它们,以便提供一个更有用的 接口。我们之所以感到Java的流库(Stream Library)异常复杂,正是由于为了创建单独一个结果流,却需要创建多个对象的 缘故。很有必要按照功能对类进行分类。库的设计者首先决定与输入有关的所有类都从InputStream继承,而与输出有关的所有 类都从OutputStream继承。 //这里写一个比较经典的读写文件的类,以文件的方式实现一个计数器
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.PrintWriter; /** * @author gms * create 2005-1-18 14:39:24 */ public class FileRw { private File f = new File("d:\\counter.txt"); public int getNum(){ int i = -1; try{ String stri=""; BufferedReader in = new BufferedReader(new FileReader(f)); while((stri=in.readLine())!=null){ //逐行读取 i = Integer.parseInt(stri.trim()); } in.close(); }catch(Exception e){ e.printStackTrace(); } return i; } public void setNum(){ int i = getNum(); i++; try{ PrintWriter out=new PrintWriter(new BufferedWriter(new FileWriter(f,false))); out.write(String.valueOf(i)); //可能是编码的原因,如果直接写入int的话,将出现java编码和windows编码的混乱,因此此处写入的是String out.close() ; }catch(Exception e){ e.printStackTrace(); } } public static void main(String[] args) { FileRw frw = new FileRw(); for(int i =0;i< 9; i++){ frw.setNum(); System.out.println(frw.getNum()); } } } 第11章 运行期类型鉴定 运行期类型鉴定(RTTI)的概念初看非常简单——手上只有基础类型的一个句柄时,利用它判断一个对象的正确类型。 然而,对RTTI的需要暴露出了面向对象设计许多有趣(而且经常是令人困惑的)的问题,并把程序的构造问题正式摆上了桌面. 本章将讨论如何利用Java在运行期间查找对象和类信息。这主要采取两种形式:一种是“传统”RTTI,它假定我们已在编译和运 行期拥有所有类型;另一种是Java1.1特有的“反射”机制,利用它可在运行期独立查找类信息。首先讨论“传统”的RTTI,再讨 论反射问题。 我们已知的RTTI形式包括: (1) 经典造型,如"(Shape)",它用RTTI确保造型的正确性,并在遇到一个失败的造型后产生一个ClassCastException违例。 (2) 代表对象类型的Class对象。可查询Class对象,获取有用的运行期资料。 第12章 传递和返回对象
到目前为止,读者应对对象的“传递”有了一个较为深刻的认识,记住实际传递的只是一个句柄。 那么一般都会问到:“Java有指针吗?”有些人认为指针的操作很困难,而且十分危险,所以一厢情愿地认为它没有好处。同时由 于Java有如此好的口碑,所以应该很轻易地免除自己以前编程中的麻烦,其中不可能夹带有指针这样的“危险品”。然而准确地说, Java是有指针的!事实上,Java中每个对象(除基本数据类型以外)的标识符都属于指针的一种。但它们的使用受到了严格的限 制和防范,不仅编译器对它们有“戒心”,运行期系统也不例外。 【稍微总结一下】 Java中的所有自变量或参数传递都是通过传递句柄进行的。也就是说,当我们传递“一个对象”时,实际传递的只是指向位于方法 外部的那个对象的“一个句柄”。所以一旦要对那个句柄进行任何修改,便相当于修改外部对象。此外: ■参数传递过程中会自动产生别名问题 ■不存在本地对象,只有本地句柄 ■句柄有自己的作用域,而对象没有 ■对象的“存在时间”在Java里不是个问题 ■没有语言上的支持(如常量)可防止对象被修改(以避免别名的副作用) 若只是从对象中读取信息,而不修改它,传递句柄便是自变量传递中最有效的一种形式。这种做非常恰当;默认的方法一般也是最 有效的方法。然而,有时仍需将对象当作“本地的”对待,使我们作出的改变只影响一个本地副本,不会对外面的对象造成影响。许 多程序设计语言都支持在方法内自动生成外部对象的一个本地副本(注释①)。尽管Java不具备这种能力,但允许我们达到同样的 效果。
①:在C语言中,通常控制的是少量数据位,默认操作是按值传递。C++也必须遵照这一形式,但按值传递对象并非肯定是一种有效 的方式。此外,在C++中用于支持按值传递的代码也较难编写,是件让人头痛的事情。 第13章 创建窗口和程序片 略 2005-3-11 
|