Java Basic Details

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Java Basic Details as PDF for free.

More details

  • Words: 9,542
  • Pages: 132
第 2 章 万事万物皆对象

一.所有对象都必须由你建立 1. 存储在哪里 1. 寄存器:我们在程序中无法控制 2. stack:存放基本类型的数据和对象的 reference,但对象本身不 存放在 stack 中,而是存放在 Heap 中 3. Heap:存放用 new 产生的数据 4. Static storage:存放在对象中用 static 定义的静态成员 5. Constant storage:存放常量 6. NON-RAM:硬盘等永久存储空间 2. 特例:基本型别 基本类型数据存放在 Stack 中,存放的是数据。而产生对象时,只把 对象的 reference 存放在 stack 中,用于指向某个对象,对象本身存 放在 Heap 中。 3. Java 中的数组 当你产生某个存储对象的数组时,真正产生的其实是存储 reference 的数组。引数组建立后,其中的每一个 reference 都会被自动设为 null,表示“不指向任何对象”。 二.建立新的数据型别:Class 1. 数据成员和函数 1.1 基本成员的缺省值

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

1) 当 class 的某个成员属于基本型别时,即使你没有为它提供初值, Java 仍保证它有一个缺省值。 2) 只有当变量身份是“class 内的成员时,Java 才保证为该变量提 供初值。 三.函数(Mehtods),引数(arguments) ,返回值(return values) 1. 引数列 当引数传递的是对象时,传递的是对象的 reference。 四.注解用内嵌式文档 Java 提供两种注解风格:/*XXXX*/、//XXXX

第 3 章 控制程序流程

一.使用 Java 运算符 1.关系运算符 1.) 当对两个对象运用关系运算符进行比较时,比较的是 object reference,如: Integer n1 = new Integer(3); Integer n2 = new Integer(3); System.out.println(n1==n2); 结果为 false,因为两个 object reference(n1 和 n2)值是不同的 2) quals()的缺省行为也是拿 referenct 来比较。不过 Java 中的 class 覆写了 equals 方法,如:

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Integer n1 = new Integer(3); Integer n2 = new Integer(3); System.out.println(n1.quals(n2));//值为 true 2. 逻辑运算符 1) 只能将 and、or、not 施用于 boolean 值身上。如果逻辑运算符 两边的值存在 non-boolean 值,将会出错,如: int test1 = 1; System.out.println((test && 1<2);// 编 辑 出 错 , test 是 non-boolean 值 3. 位移运算符 如果所操作的位移对象是 char、byte、short,位移动作发生之前, 其值会先被晋升为 int,运算结果会是 int。 二.流程控制 1. 迭代(iteration) 1.1 逗号运算符 逗号运算符只能用于 for 循环的控制表达式中的 initialization 和 step 两部分中,如:for(int i=0, j=I+1; I<5; i++, j=I*2) 1.2 break 和 continue break 表示退出循环;continue 表示退出本次循环,回来循环起始位 置。 1.3 label

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

label 只有放在迭代语句之前才起作用,在 label 和迭代语句之间插 入任何语句都不会起作用。 2. Switch switch 中的选择器必须是 int 或 char 型,如: float i = 2; switch ( i )//将出错,因为 i 不是 int 或 char 之一 3. 计算细节 1) 从 float 或 double 转为整数值,总是以完全舍弃小数的方式进 行。 4. Math.random()的输出范围是[0, 1]。

第 4 章 初始化和清理

一.以构造函数(constructor)确保初始化的进行 如果某个 class 具备构造函数,Java 便会在对象生成之际,使用者 有能力加以操作之前,自动调用其构造函数,于是便能名确保初始化 动作一定被执行。 二.函数重载(Method overloading) 1. 区分重载函数 由于只能从函数名和函数的引数列来区分两个函数,而重载函数具有 相同的函数名称,所以每个重载函数

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

都必须具备独一无二的引数列。 2. Default 构造函数 1) default 构造函数是一种不带任何引数的构造函数。如果你所开 发的 class 不具任何构造函数,编译器会自动为你生成一个 default 构造函数。 2) 如果你自行定义了任何一个构造函数(不论有无引数) ,编译器 就不会为你生成 default 构造函数。 3) 如果定义了一个 class,如 class Bush{ Bush(int I){} } 当想用 new Bush();来产生 class 的实例时,会产生错误。因为在定 义 class 时已定义了构造函数,所以编译器就不会为 class 生成 default 构造函数。当我们用 new Bush()来产生实例时,会尝试调用 default 构造函数,但在 class 中没有 default 构造函数,所以会出 错。如: class Sundae { Sundae(int i) {} } public class IceCream {

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public static void main(String[] args) { //Sundae x = new Sundae();会编译出错,无构造函数 Sundae() Sundae y = new Sundae(1); } }

*:在定义一个 class 时,如果定义了自己的构造函数,最好同时定 义一个 default 构造函数 3. 关键字 this 1) this 仅用于函数之内,能取得“唤起此一函数“的那个 object reference。 2) 在构造函数中,通过 this 可以调用同一 class 中别的构造函数, 如 public class Flower{ Flower (int petals){} Flower(String ss){} Flower(int petals, Sting ss){ //petals++;调用另一个构造函数的语句必须在最起始的位置 this(petals); //this(ss);会产生错误,因为在一个构造函数中只能调用一个构造 函数

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} } **:1)在构造调用另一个构造函数,调用动作必须置于最起始的 位置 2)不能在构造函数以外的任何函数内调用构造函数 3)在一个构造函数内只能调用一个构造函数 4. Static 的意义 无法在 static 函数中调用 non-static 函数(反向可行)。为什么不 能呢,我们看下面的例子。 例 4.2.4.1 假设能在 static 函数中调用 non-static 函数,那么(a)处就将出 错。因为在没有产生 Movie class 实例之前,在就不存在 Movie class 内的 name 实例,而在 getName()中却要使用 name 实例,显然的错误 的。 class Movie{ String name = “”; Movie(){} public Movie(String name) { this.name = name; } public static String getName() { return name; } } public class Test{ public static void main(String[] args){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

//下面两名先产生实例后再调用 getName()没有问题 //Movie movie1 = new Movie(“movie1”); //String name1 = movie1.getName(); //下面一名将出错 //String name2 = Movie.getname(); (a) } } 三.清理(cleanup):终结(finalization)与垃圾回收(garbage collection) 1)你的对象可能不会被回收 只有当程序不够内存时,垃圾回收器才会启动去回收不再被使用的对 象的内存空间。某个对象所占用的空间可能永远不会被释放掉,因为 你的程序可能永远不会逼近内存用完的那一刻,而垃圾回收器完全没 有被启动以释放你的对象所占据的内存,那些空间便会在程序终止时 才一次归还给操作系统 3) 只有在采用原生函数(native methods)时,才使用 finalize()。 四.成员初始化(member initialization) 1) 函数中的变量不会被自动初始化,如 void f(){ int i; i++; }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

将发生编译错误,因为 i 没有被初始化。 2) class 的数据成员会被自动初始化,具体情况如下(见 P220 例 子) : 基本型别:boolean:false、char:null(\u0000) 、byte:0、short: 0、int:0、 long:0 、float:0、double:0 对象(reference):null 1. 初始化次序 1) 所有变量一定会在任何一个函数(甚至是构造函数)被调用之前 完成初始化(见 P233 例子) 2) 在产生一个 class 的对象(包含 static 成员的 class 的代码被 装载)时,首先自动初始化 class 中的 static 成员变量,再执行所 有出现于 static 数据定义处的初始化动作,最后执行 static block, 所有这些初始化操作只在第一次生成该对象时进行。 3) 自动初始化 class 中的其它成员变量。 4) 执行所有出现于数据定义处的初始化动作。如:int i=1;的执 行顺序是先把 I 自动初始化为 0,再执行数据定义处的初始化动作, 初始化为 1。 5) 执行 non-static block 6) 调用构造函数。 例:

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class Cup{ Cup(int marker){ System.out.println("Cup(" + marker + ")"); } void f(int marker){ System.out.println("f(" + marker + ")"); } }

class Cups{ static Cup c1 = new Cup(11); static Cup c2; Cup c3 = new Cup(33); Cup c4; { c3 = new Cup(3); c4 = new Cup(4); } static{ c1 = new Cup(1); c2 = new Cup(2); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Cups(){ System.out.println("Cups()"); } }

public class ExplicitStatic{ public static void main(String[] args){ System.out.println("Inside main()"); Cups.c1.f(99); } static Cups x = new Cups(); static Cups y = new Cups(); }

结果为: Cup(11) Cup(1) Cup(2) Cup(33) Cup(3) Cup(4) Cups()

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Cup(33) Cup(3) Cup(4) Cups() Inside main() f(99) 2. Array 的初始化 1) 定义数组时不能指定大小。如 int[4] iArr = {0, 1, 2, 3};, 由于指定了数组的大小,会编译出错。 2) 数组只是存放 reference 的数组。Array 与 non-array 的结构图 如下: a)对于基本型别数据,存放的是数据。如 int i = 5; b)对于 class 变量,存放的是 reference,这个 reference 指向一 个存有 class 实例的内存空间。如 String s = “hello”; 变量 s 存放的是一个 reference,这个 reference 指向一个存有 String 实例的内存空间。 c)对于基本型别数组,存放的是 reference 数组,数组中的每一个 reference 都指向一个 class 实例的内存空间。如 int[] ia = {10, 11, 12};

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

数组 ia 存放的是一个 reference 数组,数组中的每一个 reference 都指向一个的 int 实例的内存空间。 d)对于 class 数组,存放的是 reference 数组,数组中的每一个 reference 都指向一个的 class 实例的内存空间。如 String[] sa = {“hello1”, “hello2”, “hello3”}; 数组 sa 存放的是一个 reference 数组,数组中的每一个 reference 都指向一个的 String 实例的内存空间。 3) 任何数组都要进行初始化,使用没有进行初始化的数组会产生运 行时错误,如: int[] iArr; System.out.pritnln(iArr[0]);//产生错误,因为 iArr 还未初始化 数组初始化可在任何地方,可用以下方法来对数组进行初始化: a) int[] iArr = {1,1,1,1};//数组的长度为{}元素的个数 b) int i = 10; int[] iArr = new int;//数组的长度可为变量(这在 C/C++中不行) System.out.println(iArr[0]);//iArr[0]是一个 int,自动初始化 值为 0 Integer[] iArr2 = new Integer; System.out.println(iArr2[0]);//iArr[0]是一个 reference,自动 初始为 null I) 对于基本型别数组,new 产生的是用于存放数据的数组;否则, 产生的只是存放 reference 的数组。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

II) new 可用来初始化基本型别的数组,但不能产生 non-array 的 基本型别数据。 c) int[] iArr = new int[]{1,1,1,1}; Integer[]

iArr2

=

new

Integer[]{new

Integer(1),

new

Integer(2)}; 3. 多维数组(Multidimensional)arrays 多维数组每一维的大小可以不一样,如: Integer[][][] a5; a5 = new Integer[3]; for(int i=0; i
第 5 章 隐藏实现细节

一.Java 访问权限饰词(access specifiers) Java 有 public、protect、friendly、private 四种访问权限,并且 这四访问权限的访问范围越来越小。 1. friendly 1) 果一个 class 内的数据成员或方法没有任何权限饰词,那么它的 缺省访问权限就是 friendly。同一个

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

package 内的其它所有 classes 都可以访问 friendly 成员,但对 package 以外的 classes 则形同 private。 2)

对于同一个文件夹下的、没有用 package 的 classes,Java

会自动将这些 classes 初见为隶属于该目录的 default package,可 以相互调用 class 中的 friendly 成员。如以下两个 class 分别在同 一个文件夹的两个文件中,虽然没有引入 package,但隶属于相同的 default package。 class Sundae{ //以下两个方法缺省为 friendly Sundae(){} Void f() {System.out.println(“Sundae.f()”); } public class IceCream{ public static void main(String[] args){ Sundae x = new Sundae(); x.f(); } } 2. public:可以被任何 class 调用 3. private:private 成员只能在成员所属的 class 内被调用,如:

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class Sundae{ private Sundae(){}//只能在 Sundae class 中被调用 Sundae(int i) {} static Sundae makASundae() { return new Sundae(); } } public class IceCream{ public static void main(String[] args){ // Sundae class 中构造函数 Sundae()是 private, // 所以不能用它进行初始化 //Sundae x = new Sundae(); Sundae y = new Sundae(1);//Sundae(int)是 friendly,可以在此 调用 Sundae z = Sundae.makASundae(); } } 4. protected:具有 friendly 访问权限的同时,又能被 subclass (当然包括子孙类,即子类的子类)所访问。即,既能被同一 package 中的 classes 访问,又能被 protected 成员所在 class 的 subclass 访问。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

二.Class 的访问权限 1.Class 同样具有 public、protect、friendly、private 四种访问 访问权限: 1)public:在任何地方都可被使用 2)protect、private:除了它自己,没有任何 class 可以使用,所 以 class 不能是 protected 或 private(inner class 除外) 3) friendly:同一个 package 中的 classes 能用 2. 如何调用构造函数被声明为 private 的 class 1) 用 static 函数 2) 用 Singteton 模式 class Soup{ private Soup(){} //(1)静态函数方法 public static Soup makeSout(){ return new Soup(); } //(2)The "Singleton" pattern: private static Soup ps1 = new Soup(); public static Soup access(){ return ps1; }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public void f(String msg){ System.out.println("f(" + msg + ")"); } } public class Lunch{ public static void main(String[] args){ //Soup priv1 = new Soup();编译错误 Soup priv2 = Soup.makeSout(); Soup priv3 = Soup.access(); priv2.f("priv2"); priv3.f("priv3"); }

第 6 章 重复运用 classes

一.继承(inheritance) 1.在 derived class 中 overriding 某个函数时, 只能覆写 base class 中的接口,即 base class 中的 public 或 protected 或 friendly 函 数。如果试图 overriding 一个 private 函数,虽然编译通过,但实 际上你只是在 derived class 中添加了一个函数。如 class Cleanser{ private void prt(){//(b)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

System.out.println("Cleanser.prt()"); } } public class ExplicitStatic extends Cleanser{ public void prt(){ System.out.println("ExplicitStatic.prt()"); } public static void main(String[] args){ Cleanser x = new ExplicitStatic(); x.prt();//(a) } } 因为 Cleanser 中的 prt()是 private,所以不能在其 derived

class

中被覆写。ExplicitStatic 中的 prt()只是 ExplicitStatic 中的一 个函数,所以当试图在(a)处通过多态来调用 prt()时,会发生错 误。如果把(b)处的 private 去掉,则结果为 ExplicitStatic.prt() 2. Super 的使用 1)通过关键字 super 可以调用当前 class 的 superclass(父类) 。 例 6.1.1.1 class Base{ Base(){System.out.println("Base()");}

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public void scrub() { System.out.println(" Base.scrub()"); } } class Cleanser extends Base{ private String s = new String("Cleanser"); public void append(String a) { s+=a; } public void dilute() { append(" dilute()"); } public void apply() { append(" apply()"); } public void scrub() { append(" scrub()"); } public void print() { System.out.println(s); } Cleanser(){ System.out.println("Cleanser(): " + s); } public static void testStatic(){ System.out.println("testStatic()"); } public static void main(String[] args){ Cleanser x = new Cleanser(); x.dilute(); x.apply(); x.scrub(); x.print(); } } public class ExplicitStatic extends Cleanser{ ExplicitStatic(){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

System.out.println("ExplicitStatic()"); } public void scrub(){ append(" Detergen.scrub()"); super.testStatic(); super.scrub();//调用的是 Cleanser.scrub() } public void foam() { append(" foam()"); } public static void main(String[] args){ ExplicitStatic x = new ExplicitStatic(); x.dilute(); x.apply(); x.scrub(); x.foam(); x.print(); System.out.println("Test base class:"); Cleanser.main(args); testStatic(); } }

运行结果: Base() Cleanser(): Cleanser ExplicitStatic() testStatic()

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Cleanser dilute() apply() Detergen.scrub() scrub() foam() Test base class: Base() Cleanser(): Cleanser Cleanser dilute() apply() scrub() testStatic() 2)通过 super 来调用 superclass 中的成员时,调用的是最近成员。 例 6.1.1.2 class Base{ protected String baseS = "Base";//(a) //private String baseS = "Base"; Base(){System.out.println("Base()");} } class Cleanser extends Base{ protected String baseS = "Cleanser";//(b) public String s = new String("Cleanser"); Cleanser(){ System.out.println("Cleanser(): " + s); } Cleanser(String a){ System.out.println("Cleanser(" + a + "): s = " + s ); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} public class ExplicitStatic extends Cleanser{ String s2 = s; String baseS = super.baseS; //(c) ExplicitStatic(){ super("ExplicitStatic"); System.out.println("ExplicitStatic():s2 = " + s2 + ", baseS = " + baseS + "super.baseS = " + super.baseS); baseS = "ExplicitStatic"; System.out.println("baseS = " + baseS + " , super.baseS = " + super.baseS); } public static void main(String[] args){ ExplicitStatic x = new ExplicitStatic(); } } 结果 1: Base() Cleanser(ExplicitStatic): s = Cleanser ExplicitStatic():s2 = Cleanser, baseS = Cleanser,super.baseS = Cleanser

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

baseS = ExplicitStatic , super.baseS = Cleanser 在上面例子中,在三个 class 中都存在 String bases 实例。在 ExplicitStatic 中如果直接调用 baseS,则实际调用的是当前类 ExplicitStatic 中的 baseS(即 (c) 处的成员) ; 如果通过 super.bases 来调用 baseS,则调用的是离当前类 ExplicitStatic 最近的 baseS 成员,即 Cleanser class 中的 baseS 实例(即(b)处) ,产生的结 果如结果 1 所示。如果把(b)处语句注释掉,则将调用 Base class 中的 baseS,结果如结果 2 所示。 结果 2: Base() Cleanser(ExplicitStatic): s = Cleanser ExplicitStatic():s2 = Cleanser, baseS = Base,super.baseS = Base baseS = ExplicitStatic , super.baseS = Base 3. Base class 的初始化 2.1 当你产生 derived class 对象时,其中会包含 base class 子对 象(subobject)。这个子对象就和你另外产生的 base class 对象一 模一样。 2.2 通过 super()可调用 base class 的构造函数,但必须放在构造 函数的第一行,并且只能在构造函数中运用。 2.3 初始化顺序为: 1) 加载代码(.class 文件)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

2) 初始化 class 的静态成员,初始化顺序了“从里到外” ,即从 base class 开始。 3) 在 derived class 的构造函数中调用 base class 的构造函数。 如果在 derived class 的构造函数中没有通过 super()显式调用调用 base class 的构造函数,编译器会调用 bass class 的 default 构造 函数并自动生成相应的调用语句,从而产生一个 base class 实例。 如果在 derived class 的构造函数中通过 super()显示调用了父类的 构造函数,则调用所指定的构造函数。调用构造函数的调用顺序是“从 里到外”。 4) 调用 derived class 的构造函数。 **:当 base class 没有 default 构造函数时, 必须在 derived class 的构造函数中通过 super 显示调用 base class 的构造函数。 例:下面代码的初始化过程为: 1)装载 ExplicitStatic 的代码 (装载 ExplicitStatic.class 文件)。 2) 发现 ExplicitStatic 有关键字 extends,装载 ExplicitStatic 的 base

class 的代码(装载 Cleanser.class 文件)。

3) 发现 Cleanser 有关键字 extends,装载 Cleanser 的 base

class

的代码(装载 Base.class 文件) 。 4) 初始化 Base class 中的静态成员。 5) 初始化 Cleanser class 中的静态成员。 6) 初始化 ExplicitStatic class 中的静态成员。 如果把(c)处的代码注释掉,那么初始化工作到此就结束了。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

7) 为 ExplicitStatic 对象分配存储空间,并把存储空间初始化为 0。 8)在 ExplicitStatic class 的构造中调用 super("ExplicitStatic") (在 ExplicitStatic class 的构造函数中显式调用父类的构造函 数) ,试图产生一个 Cleanser class 实例。 9) 为 Cleanser 对象分配存储空间,并把存储空间初始化为 0。 10) 由于 Cleanser class 又是继承自 Base class,会在 Cleanser class 的构造函数中通过 super()(由于没有显式调用父类的构造函 数,所以自动调用父类的 default 构造函数)调用父类的构造函数, 试图产生一个 Cleanser class 实例。 11) 产生一个 Base class 实例。先初始化成员变量,再调用构造函 数。 12) 回到 Cleanser class,产生一个实例。首先初始化 Cleanser class 中的成员数据,再执行构造函数 Cleanser(String a)中的其余部分。 13) 回到 ExplicitStatic class, 产生一 个实 例。首 先初 始化 ExplicitStatic class 中 的 成 员 数 据 , 再 执 行 构 造 函 数 ExplicitStatic

()









(System.out.println(“ExplicitStatic()”))。 class Base{ static int s1 = prt("s1 initialized.", 11); int i1 = prt("i1 initialized.", 12);

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn





Base(){ System.out.println("Base()"); System.out.println("s1 = " + s1 + " ,i1 = " + i1); draw();//(d) } void draw(){ System.out.println("base.draw:s1 = " + s1 + " ,i1 = " + i1); } static int prt(String s, int num) { System.out.println(s); return num; } } class Cleanser extends Base{ static int s2 = prt("s2 initialized.", 21); int i2 = prt("i2 initialized.", 22); Cleanser(){ System.out.println("Cleanser()"); System.out.println("s2 = " + s2 + " ,i2 = " + i2); } Cleanser(String a){ //super();(b)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

System.out.println("Cleanser(" + a + ")"); System.out.println("s2 = " + s2 + " ,i2 = " + i2); }

void draw(){ System.out.println("Cleanser.draw:s2 = " + s2 + " ,i2 = " + i2); } } public class ExplicitStatic extends Cleanser{ static int s3 = prt("s3 initialized.", 31); int i3 = prt("i3 initialized", 31); ExplicitStatic(){ super("ExplicitStatic");//(a) System.out.println("ExplicitStatic()"); System.out.println("s3 = " + s3 + " ,i3 = " + i3); } public static void main(String[] args){ ExplicitStatic x = new ExplicitStatic();//(c) } }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

结果: s1 initialized. s2 initialized. s3 initialized. //如果把(c)处的代码注释掉,输出结果到此为止,不会输出下面 结果 i1 initialized. Base() s1 = 11 ,i1 = 12 Cleanser.draw:s2 = 21 ,i2 = 0//(d)处结果 i2 initialized. Cleanser(ExplicitStatic)//(a)处结果 s2 = 21 ,i2 = 22 i3 initialized ExplicitStatic() s3 = 31 ,i3 = 31 由于在 Base()中调用 draw()时,Cleanser 中的 i2 还未进行初始化, 而在为 Cleanser 对象分配存储空间时,把存储空间初始化为 0,所 以此时 i2 为 0。 2.4 代码及结果中的(a)说明了是先产生当前 class 的 base class 的实例,否则在 derived class 中无法

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

调 用 base class 的 成 员 。 在 调 用 Cleanser class 的 构 造 函 数 Cleanser(String a)时,在 Cleanser(String a)中没有用 super 显式调用 Base class 的构造函数,所以系统会自 动生成调用 Base class 的 default 构造函数的语句,如(b) 。 4. 组合与继承之间的快择 . 1)继承表示的是一种“is-a(是一个) ”的关系,如货车是汽车中 的一种;组合表示的是一种“has-a(有一个) ”的关系,如汽车有四 个轮子。 2)是否需要将新的 class 向上转型为 base class。 5. 在继承中的访问权限 protect 变量能被子孙类所调用。如 Base class 中的 baseS 能被 Cleanser class 和 ExplicitStatic class 调用。 class Base{ protected String baseS = "Base"; //private String baseS = "Base"; Base(){System.out.println("Base()");} } class Cleanser extends Base{ protected String baseS = "Cleanser"; public String s = new String("Cleanser"); Cleanser(){ System.out.println("Cleanser(): " + s);

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} Cleanser(String a){ System.out.println("Cleanser(" + a + "): s = " + s ); } } public class ExplicitStatic extends Cleanser{ String s2 = s; String baseS = super.baseS; ExplicitStatic(){ super("ExplicitStatic"); System.out.println("ExplicitStatic():s2 = " + s2 + ", baseS = " + baseS + "super.baseS = " + super.baseS); baseS = "ExplicitStatic"; System.out.println("baseS = " + baseS + " , super.baseS = " + super.baseS); } public static void main(String[] args){ ExplicitStatic x = new ExplicitStatic(); } }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

结果: Base() Cleanser(ExplicitStatic): s = Cleanser ExplicitStatic():s2 = Cleanser, baseS = Cleanser, super.baseS = Cleanser baseS = ExplicitStatic , super.baseS = Cleanser 二.关键字 final 1.Final data 1.1

final data

1)当基本型别被定义为 final,表示它的数据值不能被改变。如 final int i = 9; i++;//编译错误,不能改变 I 的值 2) 当 object

reference 被定义为 final 时,不能改变的只是

reference 而不是对象本身。如 class Value{ int i = 1; } public class ExplicitStatic extends Cleanser{ public static void main(String[] args){ final Value v = new Value();//v.i = 1 v.i++;//v.i = 2 //v = new Value();

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} } 由于 v 为 final,所以不能通过 new Value()使 v 重新指向一个对象; 但是 v 所指向的对象的值是可以改变的(v.i++)。 1.2

blank finals

我们可以将数据成员声明为 final 但不给予初值,这就是 blank finals。但 blank

finals 必须且只能在构造函数中进行初始化。

public class ExplicitStatic { final int ib; final int i = 1; ExplicitStatic() { ib = 2;//(a) //i = 3;

(b)

System.out.println("i = " + i + ", ib = " + ib); } public static void main(String[] args){ ExplicitStatic ex = new ExplicitStatic(); } }

ib 为 blank

finals,所以可以在构造函数中进行初始化。如果把(a)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

处的代码注释掉,则 ib 没有初值,编译出错。而 i 在定义处已进行 了初始化,则不能改变 i 的值,(b)处的代码编译错误。 **:非 blank 2.Final

finals 成员即使在构造函数中也不能更改其值

methods

1)被声明为 final 的函数不能被覆写 2)class 中所有 private 函数自然而然会是 final。 3. Final

classes

1)当一个 class 被声明为 final 时,表示它不能被继承,但 class 的数据成员不是 final,可以被改变。如 class SmallBrain{} final class Dinosaur{ int i = 7; int j = i; SmallBrain x = new SmallBrain(); void f(){}; } //不能继承 final 函数 //class Further extends Dinosaur{} public class ExplicitStatic{ public static void main(String[] args){ Dinosaur n = new Dinosaur(); n.f();

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

n.i = 40;//final class 中的 non-final 数据成员可以被改变 n.j++; } } 2)final

class 中的所有函数也都自然是 final,因为没有人能够

加以覆写。

第 7 章 多态

一.再探向上转型(upcasting) 将某个 object

reference 视为一个“reference

to

base

type

“的动作,称为向上转型。 1. Upcasting 后调用某个函数时,如果 derived 该函数,则会调用 derived

class 中覆写了

class 中的函数;否则,会调用 base

class 中的函数。如 class First{ public void prt(){ System.out.println("First"); } } class Second extends First{ //(a)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public void prt(){ System.out.println("Second"); } } public class ExplicitStatic{ public static void main(String[] args){ First n = new Second(); n.prt();; } } 结果为 Second。如果当 Second

class 中的 prt()函数注释掉,将输

出 First。 2. 向上转型后只能调用 base

class 中被 derived

函数。 /* abstract class First{ int i = 122; public void prt(){ System.out.println("First.i = " + i); } public abstract void prt(First f); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class 覆写的

class Second extends First{ public void prt(){ System.out.println("Second.i = " + i); }

public void prt(First i) {

}

public void prt(int i) {

} } public class ExplicitStatic{ public static void main(String[] args){ First n = new Second(); n.prt(2);; } }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

*/ class First{ public void prt(){ System.out.println("First"); } } class Second extends First{ //(a) public void prt(){ System.out.println("Second"); } public void prt(int i){//(a) System.out.println("Second.i = " + i); } } public class ExplicitStatic{ public static void main(String[] args){ First n = new Second(); n.prt(3); } }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

(a) 处的函数只是 Second

class 中的函数, 所以不能通过 n.prt(3)

进行调用。 二.Abstract

class 和 Abstract

methods

1. 如果一个 class 中存在 abstract 明为 abstract

class。

2. abstract

class 不能被实例化。

3.如果 base

class 是一个 abstract

必须实现 base

class,则 class 也必须被声

class, 那么 derived

class 中所有的 abstract

class 也必须被声明为 abstract

class

methods;否则,derived

class。

三.其它要点 1. 纯粹继承与扩充 纯粹继承:只有 base

class 所建议的函数,才被 derived

加以覆写。 扩充:除了覆写 base

class 的函数,还实现了自己的函数

abstract class First{ public abstract void f(); public abstract void g(); } //纯粹继承 class Second extends First{ public void f(){} public void g(){}

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class

} //扩充 class Third extends First{ public void f(){} public void g(){} public void u(){}//base

class 不存在的函数

} 2. 向下转型 1) 向下转型时只能调用 base 2) 只有本来就为 derived

class 中被覆写过的函数

class 对象时才能正确向下转弄。

class First{ public void f(){} public void g(){} } class Second extends First{ public void f(){} public void g(){} public void u(){} public void v(){} } public class ExplicitStatic{ public static void main(String[] args){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

First[] x = {new First(), new Second()}; x[0].f(); x[1].g(); //!x[1].u();class

First 中不存在函数 u()

//((Second)x[0]).f();(a) ((Second)x[1]).u(); } }

第 8 章 接口与内隐类

一. 接口 1. 如果实现接口的 class 未实现接口中的所有函数,则这个 class 必须被声明为 abstract class 中为 abstract

class,而接口中未被实现的函数在这个

class。

interface Interface{ public void f(); public void g(); } abstract class First implements Interface{ public void f(){} }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class Second extends First{ public void g(){} } public class ExplicitStatic{ public static void main(String[] args){ Interface f = new Second(); f.f(); f.g(); } } 2. 接口中的所有函数自动具有 public 访问权限,所以实现某个接 口时,必须将承袭自该接口的所有函数都定义为 public interface MyInterface { public void f(); void g(); } class First implements MyInterface { public void f(){} //!void g(){}出错,应定义为 public } 3. 接口中的数据成员自动成为 static 和 final

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

interface MyInterface{ int i = 5; void f(); void g(); } class First implements MyInterface { public void f(){} public void g(){} } public class ExplicitStatic{ public static void main(String[] args){ MyInterface x = new First(); // MyInterface 的数据成员 I 为 static,可直接调用 System.out.println("MyInterface.i = " + MyInterface.i + " , x.i = " + x.i); // MyInterface 的数据成员 I 为 final,不能修改 //x.i++; // MyInterface.i++; } } 4. 多重继承 1) devriced

class 可以同时继承多个 interface 和一个 abstract

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

或 concrete

base

class 。 如 果 同 时 继 承 了 base

class 和

interface,那么要先写下具象类的名称,然后才是 interfaces 的名 称。 2) 如果 derived

class 所继承的具象类具有与 interfaces 相同的

函数,则可在 derived

class 不实现那个函数。

interface CanFight{ void fight(); } interface CanSwim{ void swim(); } class ActionCharacter{ public void fight(){} } class Hero extends ActionCharacter implements CanFight, CanSwim{ public void swim(){}; } public class ExplicitStatic{ static void f(CanFight x) { x.fight(); } static void s(CanSwim x) { x.swim(); } static void a(ActionCharacter x) { x.fight(); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

static void h(Hero x){ x.fight(); x.swim(); } public static void main(String[] args){ Hero h = new Hero(); f(h); s(h); a(h); h(h); } } 因为在 ActionCharacter

class 中有与接口 CanFight 完全相同的函

数 fight(),所以在 Hero

class 可以不实现 fight()方法。当要调

用 x.fight()时,会调用 ActionCharacter

class 中的 fight()函数。

3) 接口的合并时的名称冲突问题 interface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } }

class C2 implements I1, I2{ public void f() {} public int f(int i) { return 1; } } class C3 extends C implements I2{

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public int f(int i) { return 1; } } class C4 extends C implements I3{ public int f() { return 1; } } //class C5 extends C implements I1{}

(a)

//class C6 extends C implements I1{ public void f(){} } interface I4 extends I1, I3{}

(b)

//(c)

class C7 implements I4{ public void f() {} public int f() { return 1; } }

(a)处代码会产生 以下错误: method f() in class C cannot implement method f() in interface I1 with different return type, was void。(b)处代码也是错误的: method f() in class C6 cannot override method f() in class C with different return type, was int。由(b)处代码也可看出,虽然你试图实现接口 I1 中的函数, 但由于 extends C 在前,所以编译器会把 C6 中的函数看成是覆写 class

C 中的函数,而不是象你想象中的作为实现接口中的函数的

函数。 (c)处代码在原书中(P253)说会出错,但我在测试时并没发 生错误。但当你试图通过 C7 来实现接口 I4 时,是无论如何也不可能

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

编译通过的。 4) Java 中唯一可以使用多重继承的地方 Java 是不允许通过关键字 extends 来实现多重继承的,但除了通过 多重继承来扩充接口除外。 interface I1{ void f1(); } interface I2{ void f2(); } interface Ie1 extends I2{ void fe1(); } class Ce1 implements Ie1{ public void f2() {} public void fe1() {} } interface Ie2 extends Ie1, I1{ void fe2(); } class Ce2 implements Ie2{ public void fe2() {}

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public void f2() {} public void fe1() {} public void f1() {} } 接口 Ie2 继承了两个接口。 5. 嵌套的 interfaces 嵌套的 interfaces 可以在定义该内部接口的外部类(接口)之外被 使用(但内隐类不行)。 1) 当接口嵌套于 class 中 a) 不论接口为 public、friendly 或 private,都可被实现为 public、 friendly、private 三种嵌套类。 b) 被声明为 private 的接口不能在 class 外被使用。 class A{ private interface B{ void f(); } public class BImp implements B{ public void f() {} } private class BImp2 implements B{ public void f() {} }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public B getB() { return new BImp(); } private B dRef; public void recivedD(B d){ dRef = d; dRef.f();; } } public class ExplicitStatic{ public static void main(String[] args){ A a = new A(); //(a) //A.B ab = a.getB();

(b)

//A.BImp = a.getB();

(c)

a.recivedD(a.getB()); } } 虽然 A

class 含有接口,但它仍可被实例化,如(a) 。

由于接口 B 为 private,所以在(b)处调用接口 B 时会出错。但当 把接口 B 声明为 public 时, (b)将通过编译。但(c)处依然会出错, 因为内隐类的作用域为定义该内隐类的外部类内(见内隐类) 。 2) 当接口嵌套于接口中 1) 嵌套于接口中的接口自动为 public,且只能为 public。 2) 当实现某个接口时,无需实现其中嵌套的接口。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

3) Private 接口无法在其所定义的 class 之外被实现。 二. Inner classes(内隐类) 1. 内隐类的基本用法 1) 如果要在外围 class 的 non-static 函数之外产生一个 inner class 对象,得以 OuterClassName.InnerClassName 的形式指定该对 象的型别。而在 non-static 函数内则不用。 public class ExplicitStatic{ class Contents{ private int i = 11; public int value() { return i; } } class Destination{ private String label; Destination(String whereTo){ label = whereTo; } String readLabel() { return label; } } public Destination to(String s){ //在 outer class 的 non-static 函数中可直接产生 inner class 对 象 return new Destination(s); //(1)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} public Contents cont(){ return new Contents(); //(1) } public void ship(String dest){ //在 outer class 的 non-static 函数中可直接通过 InnerClassName //来指定对象型别 Contents c = cont(); Destination d = to(dest); System.out.println(d.readLabel()); } public static void main(String[] args){ ExplicitStatic p = new ExplicitStatic(); p.ship("Tanzania"); ExplicitStatic q = new ExplicitStatic(); //在 outer class 的非 non-static 函数内产生 inner class 对象 ExplicitStatic.Contents c = q.cont(); ExplicitStatic.Destination d = q.to("Borneo"); //不能在 static 函数直接生成 inner class 对象 // new Contents(); } }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

2) 对于 non-static

inner

class,在外围 class 的 non-static

函数可以通过 new 产生一个 inner class 对象,如上面的(1)处。 但要在非 non-static 函数产生一个 inner class 对象,则一定要关 联到其 enclosing

class 的某个对象。

3) inner class 的向上转型 当把一个 inner class 对象向上转型成为 interface 时,我们得到的 只是一个 reference。 interface Destination{ String readLabel(); } interface Contents{ int value(); } class Parcel3{ private class PContents implements Contents{ private int i = 11; public int value() { return i; } } protected class PDestination implements Destination{ private String label; PDestination(String whereTo){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

label = whereTo; } public String readLabel() { return label; } } public Destination to(String s){ return new PDestination(s); } public Contents cont(){ return new PContents(); } } public class ExplicitStatic{ public static void main(String[] args){ Parcel3 p = new Parcel3(); //把 inner class 对象向上转型 Contents c = p.cont(); Destination d = p.to("Borneo"); } } 虽然我们不能在 ExplicitStatic class 无法调用 Pcontents class, 但我们把一个 Pcontents

class 对象

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

向上转型为 Contents,就可对之进行调用。 4) inner inner

class 的作用域为定义该 inner

class 的 scope 内。但

class 可在它的作用域之外被继承(见 4) 。

interface Contents{ int value(); } class Parcel3{ //PContents1

class 的作用域为 Parcel3

class 内

private class PContents1 implements Contents{ private int i = 11; public int value() { return i; } } public Contents cont1(){ return new PContents1(); } public Contents cont2(){ //PContents2

class 的作用域为函数 cont2 内

class PContents2 implements Contents{ private int i = 11; public int value() { return i; } } return new PContents2();

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} //不能在函数 cont2 外使用 PContents2

class

/* public Contents cont22(){ return new PContents2(); } */ public Contents cont3(boolean b){ if(b){ //PContents3

class 的作用域为当前 if 内

class PContents3 implements Contents{ private int i = 11; public int value() { return i; } } return new PContents3(); } //不能在 if 外使用 PContents3

class

//return new PContents3(); return null; } } public class ExplicitStatic{

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

public static void main(String[] args){ Parcel3 p = new Parcel3(); Contents c1 = p.cont1(); Contents c2 = p.cont2(); Contents c3 = p.cont3(true); } }

2. 内隐类与外围 enclosing class 的连接关系 2.1

non-static

1) inner

inner

class

class 可以访问 enclosing

private 成员),就像 inner

class 自己拥有这些成员一样。即 inner

class 天生具有对 enclosing 2) Inner

class 的所有成员(包括

class 的所有成员的访问权力。

class 对象被产生时,一定要关联到其 enclosing

的某个对象(这个 enclosing 制造者)。建构 inner

class 对象就是 Inner

class

class 对象的

class 对象的同时,得有其 enclosing

class

对象的 reference 才行。 原因:因为 inner

class 可以访问 enclosing

那么当产生一个 inner

class 时,编译器会自动为 inner

象 添 加 一 个 指 向 enclosing

class 对

class 对 象 的 reference ( 这 个

reference 是隐藏的)。所以 Inner 其 enclosing

class 的所有成员,

class 被产生时,一定要关联到

class 的某个对象。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

3) 同一个 enclosing

class 对象产生出来的 inner

问的是同一个 enclosing

class 对象访

class 对象中的成员。

interface Destination{ String readLabel(); } interface Contents{ int value(); } class Parcel3{ int i1 = 10; private String s1 = "Parcel3_"; Parcel3(String s){ s1 += s; } private class PContents implements Contents{ //可调用 enclosing

class 的成员

(1)

private int i2 = i1; private String s2 = s1; PContents(int num){ System.out.println("" + num + ": i2 = " + i2 + ",s2 = " + s2); } public int value() { return 1; }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} public Contents cont(int i){ return new PContents(i); } } public class ExplicitStatic{ public static void main(String[] args){ Parcel3 p1 = new Parcel3("1"); Contents c1 = p1.cont(1); Contents c2 = p1.cont(2); Parcel3 p2 = new Parcel3("2"); c2 = p2.cont(3); c2 = p1.cont(4); } }

结果为: 1: i2 = 10,s2 = Parcel3_1 2: i2 = 10,s2 = Parcel3_1 3: i2 = 10,s2 = Parcel3_2 4: i2 = 10,s2 = Parcel3_1 在(1)在 inner

class 调用了 enclosing

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class 的成员。结果表

明,同一个 enclosing 用的是同一个 enclosing

class 对象 p1 产生的 inner

class 对象调

class 对象中的成员,如结果中的 1、2、

4。 2.2

Static inner classes(静态内隐类)

1) 产 生 Static inner classes 对 象 时 , 不 需 要 同 时 存 在 一 个 enclosing

class 对象

2) 只能在 Static inner classes 对象中访问 enclosing

class 中

的静态成员。 interface Contents{ int value(); } class Parcel1{ private static String s1 = "Parcel3_"; private String s11 = “Parcel3_”; Parcel1(String s){ s1 += s; } protected static class PContents implements Contents{ //只能访问 enclosing class 中的 s1 String s2 = s1; //s11 不是 static 成员,不能访问 //String 22 = s11;

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

PContents(int num){ System.out.println("" + num + ":s2 = " + s2); } public int value() { return 1; } } public static Contents cont(int i){ return new PContents(i); } } public class ExplicitStatic{ public static void main(String[] args){ Parcel1 p1 = new Parcel1("1"); Contents c1 = p1.cont(1); c1 = Parcel1.cont(2);

//(1)

Parcel1 p2 = new Parcel1("2"); c1 = p2.cont(3); c1 = Parcel1.cont(4);

//(1)

} } 因为内隐类 Pcontents enclosing

class 是静态的,所以在(1)处不通过

class 对象而是通过静态函数来直接产生其对象。

2.3 无论 inner

class 被嵌套置放的层次有多深,且所有 outer

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class 的成员都可被它访问。 class MNA{ private void f() {} class A{ private void g() {} class B{ void h(){ g(); f(); } } } } 3. 如何产生 inner 3.1

class 对象的总结

non-static 内隐类

1) 在 enclosing

class 的 non-static 函数中可以直接通过 new 来

产生 2) 在 enclosing

class 的 static 函数或其它的 class 中,必须同

时存在一个 enclosing

class 对象(原因在上面 2.1 已说明) 。

interface Contents{ int value(); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class Parcel1{ protected class PContents implements Contents{ public int value() { return 1; } } public Contents cont(){ //在 non-static 函数中直接通过 new 来产生 PContents class 对象 return new PContents(); } public static void test(String[] args){ Parcel1 p1 = new Parcel1(); //在 static 函数中通过外部类 Parcel1 对象来产生 Contents c1 = p1.cont(); //调用函数 c1 = p1.new PContents();

//通过 new

} } public class ExplicitStatic{ public static void main(String[] args){ //通过外部类 Parcel1 对象来产生 Parcel1 p1 = new Parcel1(); Contents c1 = p1.cont(); //调用函数 c1 = p1.new PContents(); //通过 new }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

} 3.2

static 内隐类

1) 除了可用产生 non-static 内隐类对象的方法来产生之外,也可以 不通过已存在一个 enclosing

class 对象来产生。

interface Contents{ int value(); } class Parcel1{ protected static class PContents implements Contents{ public int value() { return 1; } } public Contents cont(){ //在 non-static 函数中直接通过 new 来产生 PContents class 对象 return new PContents(); } public static Contents cont1(){ //在 static 函数中直接通过 new 来产生 PContents class 对象 return new PContents(); //(1) } public static void test(String[] args){ Parcel1 p1 = new Parcel1(); //在 static 函数中通过外部类 Parcel1 对象来产生

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Contents c1 = p1.cont(); //调用函数 c1 = p1.new PContents(); //通过 new //在 static 函数中直接通过 new 来产生 PContents class 对象 c1 = new PContents(); //(1) } } public class ExplicitStatic{ public static void main(String[] args){ //通过外部类 Parcel1 对象来产生 Parcel1 p1 = new Parcel1(); Contents c1 = p1.cont(); //调用函数 c1 = p1.new PContents(); //通过 new //直接产生 c1 = Parcel1.cont1(); //(2) } } 上面的(1)和 9(2)中的代码只有在 Pcontents

class 为 static

时才能通过。(1)不能通过的原因见 2.1。 4. inner 1) inner

class 的继承 class 可被继承。inner

class 的 drived

drfault 构造函数必须传入一个 reference 指

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class 的

向 outer

object,并在构造函数中调用 outer

class WithInner{ class Inner{} } class InheritInner extends WithInner.Inner { //InheritInner(){} 编译错误 InheritInner(WithInner wi) { wi.super(); } } public class ExplicitStatic{ public static void main(String[] args){ WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } } 2) 覆写 inner

class 不具备多态特性。

class Egg{ class Yolk{ public Yolk(){ System.out.println("Egg.Yolk()"); }

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class 的构造函数。

} private Yolk y; public Egg(){ System.out.println("New Egg()"); y = new Yolk(); //(1) } } class BigEgg extends Egg{ //(2)尝试覆写 inner

class

class Yolk{ public Yolk(){ System.out.println("BigEgg.Yolk()"); } } } public class ExplicitStatic{ public static void main(String[] args){ new BigEgg(); //(3) } } 结果为: New Egg()

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Egg.Yolk() 在(2)中我们尝试覆写 inner

class。当通过(3)产生一个 BigEgg

时,会调用 Egg 的构造函数。在 Egg 的构造函数的(1)处产生的是 Egg.Yolk

class 对象,而不是子类 BigEgg.Yolk

**:如上所示,上述两个 inner

class 对象。

class 是完全独立的个体,各有

其专属的命名空间。

第 10 章 通过异常处理错误

一. 基本异常 1. 抛出异常的原理 1) 像产生一个 Java 对象那样在 heap 上以 new 产生一个异常对象。 2) 停止目前的执行路线,将上述那个异常对象的 reference 自目前 的 context 丢出。 3) 异常处理机制接手工作,寻找得以继续执行的适当地点。 2. 产生一个异常对象 异常类有两个构造函数:一个 default 构造函数;一个带 String 型 参数的构造函数,参数的信息可以通过异常类中的各种方法取出。 3. 异常类的结构 1) Error 是一些编译期错误或系统错误,一般无需在程序中捕捉到 Error 异常。 2) Exception 是我们能捕捉到的异常,其中 Exception 异常又分为

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

RuntimeException 和 non-RuntimeException 两大类异常。 二. 异常的捕捉和处理 1. 异常的捕捉 1.1 通过 try…catch 就可捕捉异常 import java.lang.RuntimeException; import java.lang.NullPointerException; import java.sql.SQLException; import java.io.IOException; class TestException{ public void testSQLException() throws SQLException { throw new SQLException(); } public void testIOException() throws IOException {} } public class Test{ public static void main(String[] args){ TestException te = new TestException(); try{ te.testSQLException(); te.testIOException(); } catch(SQLException ex){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

System.out.println("catch SQLException in main"); } catch(IOException ex){ System.out.println("catch IOException in main"); } catch(Exception ex){ //(1) System.out.println("catch Exception in main"); } } } 运行结果为:catch SQLException in main 只有参数类型与异常类型相同或相近的 catch 会被执行。 1.2 捕捉所有异常 如果想捕捉所有异常,只要捕捉 Exception 异常就行,如上面代码的 (1)处 2. 异常规格(exception

specification)

1) 在函数定义时可以声明异常规格。如果一个函数在异常规格中声 明了 non-RuntimeException 异常,那么当调用这个函数时,就一定 要捕捉异常规格中的 non-RuntimeException 异常。 import java.lang.RuntimeException; import java.lang.NullPointerException; import java.sql.SQLException;

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

class TestException{ //(1)异常规格中声明将抛出 RuntimeException 异常 public void testRuntime() throws RuntimeException {} //(2)异常规格中声明将抛出 NullPointerException 异常 public void testNullPointer() throws NullPointerException {} //(3)异常规格中声明将抛出 non-RuntimeException 异常 public void testNonRuntime() throws SQLException {} } public class Test{ public static void main(String[] args){ TestException te = new TestException(); te.testRuntime();

//(4)

te.testNullPointer();

//(5)

//te.testNonRuntime(); (6) try{ te.testNonRuntime(); } catch(SQLException ex){} } } 在上述代码中, (1)处在异常规格中声明将抛出 RuntimeException; (2)在异常规格中声明将抛出

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

NullPointerException





NullPointerException



RuntimeException 的子类,所以在调用这两个函数时,可不捕捉异 常,如(4) (5)处的代码一样直接调用。但(3)处在异常规格中声 明将抛出 SQLException,而 SQLException 不是 RuntimeException 的子类,所以必须捕捉 SQLException 异常。 2) 如果要在一个函数中抛出 non-RuntimeException 异常,则必须要 在异常规格中声明该异常。 import java.sql.SQLException; import java.io.IOException; class Test1{ public void f() throws SQLException{

//(2)

throw new IOException("IOException"); } } public class ExplicitStatic{ public static void main(String[] args){ Test1 te = new Test1(); try{ te.f(); } catch(Exception ex){

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

//(1)

System.out.println("catch Exception in main"); } } } 在(1)处抛出了一个没有在异常规格中被声明的 non-RuntimeException 异常,在编译时会出错。 3. 取得异常中的信息的几个函数 1) String getMessage()、getLocalizedMessage 、toString 取得异常对象中的信息 import java.lang.RuntimeException; import java.lang.NullPointerException; Import java.sql.SQLException; import java.io.IOException; class TestException{ public void tSql() throws SQLException { System.out.println("Originating the exception in tSql()"); throw new SQLException("throw in tSql"); } } public class Test{ public static void main(String[] args){ TestException te = new TestException();

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

try{ te.tSql(); } catch(SQLException ex){ System.out.println("catch SQLException in main"); System.out.println("ex.getMessage():" + ex.getMessage()); System.out.println("ex.getLocalizedMessage():" + ex.getLocalizedMessage()); System.out.println("ex.toString():" + ex.toString()); } catch(Exception ex){ System.out.println("catch Exception in main"); } } } 运行结果: Originating the exception in tSql() catch SQLException in main ex.getMessage():throw in tSql ex.getLocalizedMessage():throw in tSql ex.toString():java.sql.SQLException: throw in tSql 2) void printStackTrace()、Throwable fillStackTrace()

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

printStackTrace 打印出 Throwable 和其 call FillStackTrace 则在调用点设立新的 stack

stack

trace。

trace 信息

import java.sql.SQLException; class TestException{ public static void tSql() throws SQLException { System.out.println("Originating the exception in tSql()"); throw new SQLException("throw in tSql"); } public void f() throws SQLException{ try{ tSql(); } catch(SQLException ex){ System.out.println("In f(), e.printStackTrace()"); ex.printStackTrace(); throw ex;

//(1)

//throw (SQLException)ex.fillInStackTrace(); } } } public class Test{

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

(2)

public static void main(String[] args){ TestException te = new TestException(); try{ te.f(); } catch(SQLException ex){ System.out.println("catch in main, e.printStackTrace()"); ex.printStackTrace(); } catch(Exception ex){ System.out.println("catch Exception in main"); } } }

结果为: Originating the exception in tSql() In f(), e.printStackTrace() catch in main, e.printStackTrace() java.sql.SQLException: throw in tSql void TestException.tSql() Test.java:5

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

void TestException.f() Test.java:9 void Test.main(java.lang.String[]) Test.java:22 java.sql.SQLException

第十四章

14: 创建窗口与 Applet 设计的宗旨是"能轻松完成简单的任务,有办法完成复杂的任务"。 本章只介绍 Java 2 的 Swing 类库,并且合理假定 Swing 是 Java GUI 类库的发展方向。本章的开头部分会讲,用 Swing 创建 applet 与创 建应用程序有什么不同,以及怎样创建一个既能当 applet 在浏览器 里运行,又能当普通的应用程序,在命令行下运行程序。Swing 类库 的体系庞大,而本章的目的也只是想让你从基础开始理解并且熟悉这 些概念。如果你有更高的要求,只要肯花精力研究,Swing 大概都能 做到。 你越了解 Swing,你就越能体会到:与其它语言或开发环境 相比,Swing 是一个好得多的编程模型。而 JavaBeans (本章的临近 结尾的地方会作介绍)则是专为这个构架服务的类库。 就整个 Java 开发环境来讲,"GUI builders" (可视化编程环境)只是 一个"社交"的层面。当你用图形工具把组件放到窗体上的时候,实际 上是 GUI builder 在调用 JavaBeans 和 Swing 为你编写代码。这样不

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

仅能可以加快 GUI 的开发速度,而且能让你做更多实验。这样你就可 以尝试更多的方案,得到更好的效果了。 Swing 的简单易用与设计 合理,使你即便用 GUI builder 也能得到可读性颇佳的代码。这一点 解决了 GUI builder 的一个老问题,那就是代码的可读性。 Swing 囊括了所有比较时髦的用户界面元素:从带图像的按钮到树型 控件和表格控件,应有尽有。考虑到类库的规模,其复杂性还是比较 理想的。如果要做的东西比较简单,代码就不会很多,如果项目很复 杂,那么代码就会相应地变得复杂。也就是说入门很容易,但是如果 有必要,它也可以变得很强大。对 Swing 的好感,很大程度上源于其 "使用的正交性"。也就是说,一旦领会了这个类库的精神,你就可以 把这种概念应用到任何地方。这一点首先就缘于其标准的命名规范。 通常情况下,如果想把组件嵌套到其它组件里,直接插就行了。为了 照顾运行速度,组件都是"轻量级"的。为了能跨平台,Swing 是完全 用 Java 写的。键盘支持是内置的;运行 Swing 应用的时候可以完全 不用鼠标,这一点,不需要额外的编程。加滚动条,很容易,只要把 控件直接嵌到 JScrollPane 里就行了。加 Tooltip 也只要一行代码。 Swing 还提供一种更前卫的,被称为"pluggable look and feel(可 插接式外观)"的功能,也就是说用户界面的外观可以根据操作系统或 用户的习惯动态地改变。你甚至可以自己发明一套外观(当然是很难 的)。基本的 appletJava 能创建 applet,也就是一种能在 Web 浏览 器里运行的小程序。Applet 必须安全,所以它的功能很有限。但是 applet 是一种很强大的客户端编程工具,而后者是 Web 开发的一个

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

大课题。Applet 的限制 applet 编程的限制是很多的,所以也经常被 称作关在"沙箱"里。因为时时刻刻都有一个人??也就是 Java 的 运行时安全系统??在监视着你。不过你也可以走出沙箱,编写普通的 应用程序而不是 applet,这样就可以访问操作系统的其它功能了。 迄今为止,我们写的都是这种应用程序,只不过它们都是些没有图形 界面的控制台程序罢了。Swing 也可以写 GUI 界面的应用程序。大体 上说,要想知道 applet 能做什么,最好先去了解一下,为什么要有 applet:用它来扩展浏览器里的 Web 页面的功能。因为上网的人是不 可能真正知道这个页面是不是来自于一个无恶意的网站的,但是你又 必须确保所有运行的代码都是安全的。所以你会发现,它的最大的限 制是:Applet 不能访问本地磁盘。Java 为 applet 提供了数字签名。 你可以选择让有数字签名(由可信的开发商签署)的 applet 访问你的 硬盘,这样 applet 的很多限制就被解除了。本章的后面在讲 Java Web Start 的时候,会介绍一个这样的例子的。Web Start 是一种通过 Internet 将应用程序安全地送到客户端的方案。 Applet 的启动时间很长,因为每次都得下载所有东西,而且每下载 一个类都要向服务器发一个请求。或许浏览器会作缓存,但这并不是 一定的。所以一定要把 applet 的全部组件打成一个 JAR 的(Java ARchive)卷宗(除了.class 文件之外,还包括图像,声音),这样只 要发一个请求就可以下载整个 applet 了。JAR 卷宗里的东西可以逐 项地"数字签名"。 Applet 的优势如果你不在意这些限制,applet 还 是有明显优势的,特别是在构建 client/server 或是其他网络应用

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

的时候:没有安装的问题。Applet 是真正平台无关的(包括播放音频 文件) ,所以你用不着去为不同的平台修改程序,用户也用不着安装 完了之后再作调整。实际上每次载入有 applet 的 Web 页面时,安装 就自动完成了。因此软件的更新可以不惊动客户自动地完成。为传统 的 client/server 系统构建和安装一个新版的软件,通常都是一场恶 梦。由于 Java 语言和 applet 内置了安全机制,因此你不用担心错误 代码会破坏别人的机器。有了这两个优势,Java 就能在 intranet 的 client/server 应用里大展身手了。所谓 intranet 的 client/server 应用,是指仅存在于公司内部的,或者可以限定和控制用户环境的 (Web 浏览器和插件)特殊场合的 client/server 应用。 由于 applet 是自动集成到 HTML 里面的,因此你就有了一种与平台无 关的,能支持 applet 的文档系统了(译者注:指 HTML)。这真是太有 趣了,因为我们通常都认为文档是程序的一部分,而不是相反。 应用框架类库通常按功能进行分类。有些类库是拿来直接用的,比如 Java 标准类库里面的 String 和 ArrayList。有些类库则是用来创建 其它类的。此外还有一种被称为应用框架(application Framework) 的类库。它的目的是,提供一个或一组具备某些基本功能的类,帮助 程序员创建应用程序。而这些基本功能,是这类应用程序所必备的。 于是你写应用程序的时候,只要继承这个类,然后再根据需要,覆写 几个你感兴趣的方法,定制一下它的行为就可以了。应用框架的默认 控制机制会在适当的时机,调用那些你写的方法。应用框架是一种" 将会变和不会变的东西分开来"的绝好的例子。它的设计思想是,通

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

过覆写方法把程序的个性化部分留在本地。[76]Applet 是用应用框 架创建的。你只要继承 JApplet 类,再覆写几个方法就可以了。下面 几个方法可以控制 Web 页面上的 applet 的创建和执行:方法操作 init( ) applet 初始化的时候会自动调用,其任务包括装载组件的布局。必 须覆写。 start( ) 在 Web 浏览器上显示 applet 的时候调用。显示完毕之后,applet 才 开始正常工作,(特别是那些用 stop( )关闭的 applet)。(此外,应 用框架)调用完 init( )之后也会调用这个方法。 stop( ) 让 applet 从 Web 浏览器上消失的时候调用,这样它就能关闭一些很 耗资源的操作了。此外(应用框架调用)destroy( )之前也会先调用这 个方法。 destroy( ) 当(浏览器)不再需要这个 applet 了,要把它从页面里卸载下来的时 候,就会调用这个方法以释放资源了。注意 applet 不需要 main()。 它已经包括在应用框架里了;你只要把启动代码放到 init( )里面就 行了。JLabel 的构造函数需要一个 String 作参数。init( )方法负 责将组件 add( )到表单上。或许你会觉得,应该能直接调用它自己 (JApplet)的 add( )方法。实际上 AWT 就是这么做的。Swing 要求你 将所有的组件都加到表单的"内容面板(content pane)"上, 所以 add( )

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

的时候,必须先调用 getContentPane( )。在 Web 浏览器里运行 applet 要想运行程序,先得把 applet 放到 Web 页面里,然后用一个能运行 Java 的 Web 浏览器打开页面。你得用一种特殊的标记把 applet 放到 Web 页面里,然后让它来告诉页面该怎样装载并运行这个 applet。 这个步骤曾经非常简单。那时 Java 自己就很简单,所有人都用同一 个 Java,浏览器里的 Java 也都一样。所以你只要稍微改一下 HTML 就行了,就像这样: 但是随后而来的浏览器和语言大战使我们(不仅是程序员,还包括最 终用户)都成了输家。不久,Sun 发现再也不能指望靠浏览器来支持 风味醇正的 Java 了,唯一的解决方案是利用浏览器的扩展机制来提 供"插件(add-on)"。通过这个办法(要想禁掉 Java,除非把所有第三 方的插件全都禁掉,但是为了获取竞争优势,没有一个浏览器的提供 商会这么做的),Sun 确保了 Java 不会被敌对的浏览器提供商给禁掉。 对于 Internet Explorer,这种扩展机制就是 ActiveX 的控件,而 Netscape 的则是 plugin。你做页面时必须为这两个浏览器各写一套 标记,不过 JDK 自带了一个能自动生成标记的 HTMLconverter 工具。 下面就是用 HTMLconverter 处理过的 Applet1 的 HTML 页面:

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn


WIDTH

=

100

HEIGHT = 50 >
NAME

=

"type"

VALUE

=

"application/x-java-applet;jpi-version=1.4.1"> <EMBED type = "application/x-java-applet;jpi-version=1.4.1" CODE = Applet1 WIDTH = 100 HEIGHT = 50 scriptable = false pluginspage ="http://java.sun.com/products/plugin/index.html#download">


PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

--> code 的值是 applet 所处的.class 文件的名字,width 和 height 则 表示 applet 的初始尺寸(和前面一样,以象素为单位)。此外 applet 标记里面还可以放一些东西:到哪里去找.class 文件(codebase), 怎样对齐(align),applet 相互通讯的时候要用的标识符(name),以 及提供给 applet 的参数。参数的格式是这样的: <param name="identifier" value = "information"> 你可以根据需要,加任意多个参数。 Appletviewer 的用法 Sun 的 JDK 包含一个名为 Appletviewer 的工具, 它可以根据 标记在 HTML 文件里找出 applet,然后不显示 HTML 文本,直接运行 这个 applet。由于 Appletviewer 忽略了除 applet 标记之外的所有 其他东西,因此你可以直接把 applet 标记当作注释放到 Java 的源文 件里: // 这样你就可以用"appletviewer MyApplet.java"来启动 applet 了, 不用再写 HTML 的测试文件了。 Applet 的测试 如果只是想简单的测试一下,那么根本不用上网。直接启动 Web 浏览 器,打开含 applet 标记的 HTML 文件就可以了。浏览器装载 HTML 的 时候会发现 applet 标记,并且按照 code 的值去找相应的.class 文

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

件。当然,是在 CLASSPATH 里面找。如果 CLASSPATH 里没有,它就在 浏览器的状态栏里给一个错误信息,告诉你找不到.class 文件。如 果客户端的浏览器不能在服务器上找到.class 文件,它会到客户机 的 CLASSPATH 里面去找。这样一来,就有可能发生,浏览器在服务器 上找不到.class 文件,因此用你机器上的文件启动了 applet 的情形 了。于是在你看来一切都很正常,而其他人则什么都看不见了。所以 测试之前,一定要把本地的.class 文件(或.jar 文件)全部删了,只 有这样才能知道程序是不是呆在正确的服务器目录里。我就曾经在这 里栽过一个很冤枉的跟头。有一次,我上载 HTML 和 applet 的时候搞 错了 package 的名字,因此把 applet 放错了目录。但是我的浏览器 还能在本地的 CLASSPATH 里找到程序,所以我成了唯一一个能正常运 行这个 applet 的人了。所以给 applet 标记的 CODE 参数赋值的时候, 一定要给全名,这一点非常重要。很多公开发表的 applet 都没有打 包,但是实际工作中,最好还是打个包。用命令行启动 applet 有时 你还会希望让 GUI 程序能做一些别的事情。比方说在保留 Java 引以 为豪的跨平台性的前提下,让它做一些"普通"程序能作的事。你可以 用 Swing 类库写出与当前操作系统的外观风格完全一致的应用程序。 如果你要编写 GUI 程序,先看看是不是能最新版的 Java 及与之相关 的工具,只有是,那才值得去写。因为只有这样,才能写出不会让用 户觉得讨厌的程序。你肯定会希望能编写出既能在命令行下启动,又 能当 applet 运行的程序。这样测试 applet 的时候,会非常方便。因 为通常从命令行启动要比从 Web 浏览器和 Appletviewer 启动更快也

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

更方便。要想创建能用命令行启动的 applet,只要在类里加一个 main( ),让它把这个 applet 的实例嵌到 JFrame 里面就行了。我们 只加了一个 main( ),其它什么都没动。main( )创建了 applet 的实 例, 并把它加到 JFrame 里面,这样我们就能看到它了。 你会看到 main( ) 明确地调用了 applet 的 init( )和 start( )。这是因为,这两步原 先是交给浏览器做的。当然这并不能完全替代浏览器,因为前者还会 调用 stop( )和 destroy( ),不过大致说来,这个办法还是可行的。 如果还有问题,你可以亲自去调用。[80]注意,要是没有这行: frame.setVisible(true);那么你什么都看不见。一个专门用来显示 Applet 的框架 虽然将 applet 转换成应用程序的代码是非常有用的, 但写多了就既浪费纸张又让人生厌了。setDefaultCloseOperation( ) 的作用是,告诉程序,一旦 JFrame 被关掉了,程序也应该退出了。 默认情况下关掉 JFrame 并不意味着退出程序,所以除非你调用 setDefaultCloseOperation( )或者写一些类似的代码,否则程序是 不会中止的。制作按钮做按钮很简单:想让按钮显示什么,就拿它作 参数去调用 JButton 的构造函数。通常按钮是类的一个字段,这样就 能用它来表示按钮了。JButton 是一个组件??它有自己的小窗口??更 新的时候会自动刷新。也就是说,你不用明确地指示该如何显示按钮 或是其他什么控件;只要把它们放到表单上,它们就能自己把自己给 画出来了。所以你得在 init( )把按钮放到表单上。在往 content pane 里面加东西之前,我们先把它的"布局管理器(layout manager)"设成 FlowLayout。布局管理器的作用是告诉面板将控件放在表单上的哪个

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

位置。这里我们不能用 applet 默认的 BorderLayout,这是因为如果 用它,一旦你往表单上加了新的控件,旧控件就会被全部遮住了。而 FlowLayout 则会让控件均匀地散布在表单上,从左到右,从上到下。 捕捉事件编写事件驱动的程序是 GUI 编程的一个重要重要组成部分, 而驱动程序的基本思路就是,将事件与代码联系起来。Swing 的思路 是,将接口(图形组件)和实现(当组件发生了某个事件之后,你要运 行的代码)明明白白地分开来。Swing 组件能通报在它身上可以发生 什么事件,以及发生了什么事件。所以,如果你对某个事件不感兴趣, 比如鼠标从按钮的上方经过,你完全可以不去理会这个事件。用这种 方法能非常简洁优雅地解决事件驱动的问题,一旦你理解了其基本概 念,你甚至可以去直接使用过去从未看到过的 Swing 组件。实际上这 个模型也适用于 JavaBean。我们还是先来关心一下我们要用的这个 控件的主要事件。就这个例子而言,这个控件 JButton,而"我们感 兴趣的事件"就是按下按钮。要想表示你对按钮被按下感兴趣,可以 调用 addActionListener( )。这个方法需要一个 ActionListener 参 数 。 ActionListener 是 一 个 接 口 , 它 只 有 一 个 方 法 , actionPerformed( )。所以要想让 JButton 执行任务,你就得实现先 定制一个 ActionListener,然后用 addActionListener 方法把这个 类的实例注册给 JButton。当按钮被按下的时候,这个方法就会启动 了。(这通常被称作回调(callback))JTextField 这个组件可以显示 文本。它的文本既可以是用户输入的,也可以是程序插的。虽然创建

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

JTextField 的 方 法 有 很 多 , 但 是 最 简 单 的 还 是 把 宽 度 直 接 传 给 JTextField 的构造函数。等到 JTextField 被放上了表单,你就可以 用 setText( )来修改内容了(JTextField 还有很多方法,请参阅 JDK 文档)。JTextArea 与 JTextField 相比,JTextArea 能容纳多行文字, 提供更多的功能,除此之外它们很相似。append( )方法很有用;你 可以用它把程序的输出直接导到 JTextArea 里,这样你就能用 Swing 程序来改进我们先前写的,往标准输出上打印的命令行程序了(因为 你能用滚动条看到前面的输出了)。在把 JTextArea 加入 applet 的时 候,我们先把它嵌入 JScrollPane,这样一旦文本多了出来,滚动条 就会自动显现了。装滚动条就这么简单。相比其它 GUI 编程环境下的 类似控件,JScrollPane 的简洁与优雅真是令人折服。控制布局用 Java 把组件放到表单上的方法可能与你见过的其他 GUI 系统的都不 同。首先,它全部都是代码,根本没有"资源"这个故事。其次,组件 在表单上的位置不是由绝对定位来控制的,而是由一个被称为"布局 管理器"的对象来控制的。布局管理器的作用是根据组件 add( )的顺 序,决定其摆放的位置。组件的大小,形状,摆放的位置会随布局管 理器的不同而有明显的差别。此外布局管理器会根据 applet 或应用 程序的窗口大小,自动调整组件的位置,这样一旦窗口的大小改变了, 组件的尺寸,形状,摆放位置也会相应的作出调整。 JApplet,JFrame ,JWindow 以及 JDialog,这几个组件都有一个 getContentPane( )方法。这个方法能返回一个 Container,而这个 Container 的作用是安置(contain)和显示 Component。Container 有

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

一个 setLayout( )方法,你就是用它来选择布局管理器的。其它类, 比如 JPanel, 能直 接安 置 和显 示组 件, 所以 你 得跳 过内 容面 板 (content pane)直接设置它的布局管理器。 BorderLayout Applet 的默认布局管理器是 BorderLayout。如果没有其它指令, BorderLayout 会把所有控件全都放到表单的正中,并且拉伸到最大。 好在 BorderLayout 的功能还不止这些。它还有四个边界以及中央的 概念。当你往 BorderLayout 的面板上加控件加时,你还可以选择重 载过的 add( )方法。这时要给它一个常量。这个常量可以是下边五 个中的一个: BorderLayout. NORTH 顶边 BorderLayout. SOUTH 底边 BorderLayout. EAST 右边 BorderLayout. WEST 左边 BorderLayout.CENTER 填满当中,除非碰到其它组件或表单的边缘如果你不指明摆放对象的 区域,默认它就使用 CENTER。除了 CENTER,其它控件都会在一个方 向上压缩到最小,在另一个方向上拉伸到最大。而 CENTER 则会往两 个方向上拉伸,直至占满整个中间区域。 FlowLayout 它会让组件直接"流"到表单上,从左到右,从上到下,一行满了再换 一行。用了 FlowLayout 之后,组件就会冻结在它的"固有"尺寸上。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

比如 JButton 就会根据字符串的长度来安排大小。由于 FlowLayout 面板上的控件会被自动地压缩到最小,因此时常会出现一些意外。比 方说 ,JLabel 是根 据字 符 串来 决定 控件 的大 小 的, 所以 当你 对 FlowLayout 面板上的 JLable 控件的文本作右对齐时,显示效果不会 有任何变化。 GridLayout GridLayout 的意思是,把表单视作组件的表格,当你往里加东西的 时候,它会按从左到右,从上到下的顺序把组件放到格子里。创建布 局管理器的时候,你得在构造函数里指明行和列的数目,这样它就能 帮你把表单划分成相同大小的格子了。 GridBagLayout GridBagLayout 的作用是,当窗口的大小发生变化时,你能用它来准 确地控制窗口各部分的反应。但与此同时,它也是最难和最复杂的布 局管理器。它主要是供 GUI builder 生成代码用的(或许 GUI builder 用的不是绝对定位而是 GridBagLayout)。如果你的设计非常复杂, 以至于不得不用 GridBagLayout,那么建议你使用 GUI builder。绝 对定位还可以这样对图形组件进行绝对定位:将 Container 的布局管 理器设成 null:setLayout(null)。 往面板上加组件的时候,先调 用 setBounds( )或 reshape( )方法(根据各个版本)为组件设一个以 象素为单位的矩形。这个步骤可以放在构造函数里,也可以放在 paint( )里面,看你的需要。 有些 GUI builder 主要用的就是这个 方法,但是通常情况下,这并不是最好的方法。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

BoxLayout 由于 GridBagLayout 实在是太难学难用了,所以 Swing 引入了一个新 的 BoxLayout。它保留了 GridBagLayout 的很多优点,但是却没那么 复杂。所以当你想手工控制控件的布局时,可以优先考虑用它(再重 申一遍,如果设计非常复杂,最好还是用 GUI builder)。BoxLayout 能让你在垂直和水平两个方向上控制组件的摆放,它用一些被称为支 柱(Struts)和胶水(glue)的东西来控制组件间的距离。BoxLayout 的 构造函数同其它布局管理器稍有些不同??它的第一个参数是这个 BoxLayout 将要控制的 Container,第二个参数是布局的方向。为了 简化起鉴,Swing 还提供了一种内置 BoxLayout 的特殊容器,Box(译 者注:这里的容器是指 java.awt.Container 类)。Box 有两个能创建 水平和垂直对齐的 box 的 static 的方法,下面我们就用它来排列组 件。有了 Box 之后,再要往 content pane 里加组件的时候,你就可 以把它成第二的参数了。支柱(struts)会把组件隔开,它是大小是按 象素算的。用的时候,只要把它当作空格加在两个组件的中间就行了。 与固定间距的 struts 不同,glue(胶水)会尽可能地将组件隔开。所 以更准确的说,它应该是"弹簧(spring)"而不是"胶水"(再加上这种 设计是基于被称为"springs and struts"的思想的,因此这个术语就 显得有些奇怪了)。strut 只控制一个方向上的距离, 而 rigid area(刚 性区域)则在两个方向上固定组件间的距离。应该告诉你,对 rigid area 的评价不是那么的统一。实际上它就是绝对定位,因此有些人 认为,它根本就是多余的。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

最佳方案? Swing 的功能非常强大,短短几行代码就能做很多事。实际上你完全 可以把这几种布局结合起来,完成一些比较复杂的任务。但是,有些 时候用手写代码创建 GUI 表单就不那么明智了;首先是太复杂,其次 也不值得。Java 和 Swing 的设计者们是想用语言和类库去支持 GUI builder 工具,然后让你用工具来简化编程。实际上只要知道布局是 怎么一回事,以及事件该怎么处理(下面讲)就行了,懂不懂手写代码 控制控件的摆放,其实并不重要。你完全可以选一个趁手的工具(毕 竟 Java 的初衷就是想让你提高编程的效率)。 Swing 的事件模型 在 Swing 的事件模型中,组件可以发起(或"射出")事件。各种事件都 是类。当有事件发生时,一个或多个"监听器(listener)"会得到通知, 并做出反应。这样事件的来源就同它的处理程序分隔开来了。一般说 来,程序员是不会去修改 Swing 组件的,他们写的都是些事件处理程 序,当组件收到事件时,会自动调用这些代码,因此 Swing 的事件模 型可称得上是将接口与实现分隔开来的绝好范例了。实际上事件监听 器(event listener)就是一个实现 listener 接口的对象。所以,程 序员要做的就是创建一个 listener 对象,然后向发起事件的组件注 册这个对象。注册的过程就是调用组件的 addXXXListener( )方法, 这里"XXX"表示组件所发起的事件的类型。只要看一眼"addListener" 方法的名字就能知道组件能处理哪种事件了,所以如果你让它听错了 事件,那么编译就根本通不过。到后面你就会看到,JavaBean 在决

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

定它能处理哪些事件时,也遵循"addListener"的命名规范。事务逻 辑都应该封装成 listener。创建 listener 的唯一的条件是,它必须 实 现 接 口 。 你 完 全 可 以 创 建 一 个 " 全 局 的 listener(global listener)",但是内部类或许更合适。这么做不仅是因为要根据 UI 或事务逻辑对 listener 进行逻辑分组,更重要的是(你很快就会看 到),要利用内部类可以引用宿主类对象的特性,这样就能跨越类或 子系统的边界进行调用了。我们前面的例子里已经涉及到 Swing 的事 件模型了,下面我们把这个模型的细节补充完整。事件与监听器种类 所有 Swing 组件都有 addXXXListener( )和 removeXXXListener( ) 方法,因此组件都能添加和删除监听器。你会发现,这里的"XXX"也 正好是方法的参数,例如 addMyListener(MyListener m)。下面这张 表列出了基本的事件,监听器和方法,以及与之相对应的,提供了 addXXXListener( )和 removeXXXListener( )方法的组件。要知道, 这个事件模型在设计时就强调了扩展性,因此你很可能会遇到这张表 里没有讲到过的事件和监听器。事件,listener 接口以及 add 和 remove 方法支持这一事件的组件 ActionEvent ActionListener addActionListener( ) removeActionListener( ) JButton, JList, JTextField, JMenuItem 以 及 它 们 的 派 生 类 JCheckBoxMenuItem, JMenu,和 JpopupMenu

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

AdjustmentEvent AdjustmentListener addAdjustmentListener( ) removeAdjustmentListener( ) JScrollbar 以及实现 Adjustable 接口的组件

ComponentEvent ComponentListener addComponentListener( ) removeComponentListener( ) *Component 及其派生类 JButton, JCheckBox, JComboBox, Container, JPanel, JApplet, JScrollPane, Window, JDialog, JFileDialog, JFrame, JLabel, JList, JScrollbar, JTextArea,和 JTextField

ContainerEvent ContainerListener addContainerListener( ) removeContainerListener( ) Container 及其派生类 JPanel, JApplet, JScrollPane, Window, JDialog, JFileDialog,和 JFrame

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

FocusEvent FocusListener addFocusListener( ) removeFocusListener( ) Component 及其"派生类(derivatives*)"

KeyEvent KeyListener addKeyListener( ) removeKeyListener( ) Component 及其"派生类(derivatives*)"

MouseEvent(包括点击和移动) MouseListener addMouseListener( ) removeMouseListener( ) Component 及其"派生类(derivatives*)"

MouseEvent[81](包括点击和移动) MouseMotionListener addMouseMotionListener( ) removeMouseMotionListener( )

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Component 及其"派生类(derivatives*)"

WindowEvent WindowListener addWindowListener( ) removeWindowListener( ) Window 及其派生类 JDialog, JFileDialog,和 JFrame

ItemEvent ItemListener addItemListener( ) removeItemListener( ) JCheckBox, JCheckBoxMenuItem, JComboBox, JList, 以及实 现了 ItemSelectableinterface 的组件

TextEvent TextListener addTextListener( ) removeTextListener( ) JTextComponent 的派生类,包括 JTextArea 和 JTextField

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

一旦你知道了组件所支持的事件,你就用不着再去查文档了。你只要: 把 event 类的名字里的"Event"去掉,加上"Listener",这就是你要 实现的接口的名字了。实现上述接口,想捕捉哪个事件就实现它的接 口。比方说,如果你对鼠标的移动感兴趣,你可以去实现 MouseMotionListener 接口的 mouseMoved( )方法。(你必须实现这个 接口的全套方法,但是这种情况下,通常都会有捷径,过一会就会看 到了。) 创建一个 listener 的对象。在接口的名字前面加一个"add", 然后用这个方法向组件注册。比如,addMouseMotionListener( )。 下面是部分 listener 接口的方法: Listener 接口/Adapter 接口所定义的方法 ActionListener actionPerformed(ActionEvent)

AdjustmentListener adjustmentValueChanged(AdjustmentEvent)

ComponentListener ComponentAdapter componentHidden(ComponentEvent) componentShown(ComponentEvent) componentMoved(ComponentEvent)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

componentResized(ComponentEvent)

ContainerListener ContainerAdapter componentAdded(ContainerEvent) componentRemoved(ContainerEvent)

FocusListener FocusAdapter focusGained(FocusEvent) focusLost(FocusEvent)

KeyListener KeyAdapter keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent)

MouseListener MouseAdapter mouseClicked(MouseEvent) mouseEntered(MouseEvent)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent)

MouseMotionListener MouseMotionAdapter mouseDragged(MouseEvent) mouseMoved(MouseEvent)

WindowListener WindowAdapter windowOpened(WindowEvent) windowClosing(WindowEvent) windowClosed(WindowEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent)

ItemListener itemStateChanged(ItemEvent)

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

之所以这张表不是很全,部分是因为事件模型允许你创建自己的事件 及相关的 listener。所以你时常会碰到一些在事件类型方面自成体 系的类库,而你在本章所学到的知识会帮助你学会使用这些事件。用 listener 的 adapter 简 化 编 程 可 以 看 到 上 面 那 张 表 里 的 一 些 listener 只有一个方法。实现这种接口的工作量并不大,因为写完 方法,接口也就实现了。但是如果你要使用有多个方法的 listener 的话,事情就不那么轻松愉快了。为了解决这个问题,有些(但不是 全部)多方法的 listener 接口提供了适配器(adapter)。从上面那张 表已经列出了它们的名字。适配器会为接口提供默认的空方法。这样, 你只要继承适配器,根据需要覆写方法就可以了。adapter 就是用来 简化 listener 的创建的。但是适配器这种东西也有缺点。只是大小 写方面的一个疏漏,它就成为一个新方法了。还好,这还不是关闭窗 口时会调用的方法,所以最坏的结果也只是不能实现预期的效果。虽 然有种种不方便,但接口却能保证让你实现所有应该实现的方法。跟 踪多个事件 Swing 组件的一览表现在你已经知道布局管理器和事件 模型了,接下来就要学习怎样使用 Swing 组件了。这部分只是一个大 致的介绍,我们讲的都是常用的 Swing 组件及其特性。 记住:JDK 文档里有所有 Swing 组件的资料。 Swing 事件的命名规范 比较合理,所以要猜该怎样编写和安装事件处理程序也比较简单。用 我们前面讲的 ShowAddListeners.java 来检查组件。如果事情开始变 得复杂了,那么恭喜你毕业了,该用 GUI Builder 了。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Button Swing 收录了很多 Button,包括各种按钮,check box, radio button, 甚至连菜单项(menu item)都是继承 AbstractButton 的(鉴于菜单项 也牵涉进来了,(AbstractButton)可能还是叫"AbstractSelector" 或其它什么名字更好一些)。 Button 组 如果想让 radio button 以"几选一"的方式运行(译者注:原文为 exclusive or,字面的意思是"排他性的逻辑与"),你就必须把他们 加到一个"button 组(button group)"里。但只要是 AbstractButton, 都可以加进 ButtonGroup。这几步给原本很简单任务加了点难度。要 让 button 做到"几选一",你必须先创建一个 button 组,然后把要加 进去的 button 加进去。 Icon Icon 能用于 JLabel 和 AbstractButton(包括 JButton,JCheckBox, JRadioButton 以及 JMenuItem)。把 Icon 用于 JLabel 的语法非常简 单你也可以使用你自己的 gif 文件。如果想打开文件读取图像,只要 创建一个 ImageIcon,并且把文件的名字传给它就行了。接下来,你 就可以在程序中使用这个 Icon 了。很多 Swing 组件构造函数都可以 拿 Icon 作参数,不过你也可以用 setIcon( )方法添加或修改 Icon。 Tool tips 几乎所有与 GUI 相关的类都继承自 JComponent,而 JComponent 又包 含了一个 setToolText(String)方法。因此不管是哪种组件,只要能

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

放到表单上,你几乎都可以用(假设这是个 JComponent 的派生类对象 jc)jc.setToolTipText("My tip");来设置 tool tip。这样只要鼠标 停在 JComponent 上一段时间,旁边就会跳出一个提示框,里面就是 你设置的文本。 Text fields Borders JComponent 里面有一个 setBorder( )方法,它能让你为各 种可视组件安上有趣的边框。你还可以创建你自己的边框,然后把它 放到按钮(button),标签(label)或者其它控件里面,??只要它是继 承 JComponent 的就行。 JScrollPanes 大多数时候,你只需要 JScrollPane 能干好它的本职工作,但是你也 可以去控制它,告诉它显示哪根滚动条??垂直的,水平的,还是两个 都显示或者两个都不显示。通过给 JScrollPane 的构造函数传不同的 参数,可以控制它的滚动条。一个袖珍的编辑器不用费多大的劲, JTextPane 已 经 提 供 了 很 多 编 辑 功 能 。 applet 的 默 认 布 局 是 BorderLayout。所以,如果你什么都不说,直接往面板里面加组件, 那么它会填满整个面板。但是如果你指明其位置(NORTH,SOUTH, EAST,或 WEST),控件就会被钉在这个区域里。注意 JTextPane 的内 置功能,比如自动换行。此外它还有很多其他功能,具体详情请参阅 JDK 文档。 Check boxes

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Check box 能让你做逐项的开/关选择。它由一个小框和一个标签组 成。一般来说,选中之后框里会有一个'x'(或者其它什么表示选中的 标记),否则就是空的。一般来说,你用构造函数创建 JCheckBox 的 时候,会传给它一个用作标签的参数。JCheckBox 创建完了之后,你 还可以随时读取或设置它的状态,或者读取或重设它的标签。不管是 选中还是清除,JCheckBox 都会引发一个事件。捕捉方法同按钮事件 完全相同:用 ActionListener。 Radio buttons GUI 编程里的 radio button 的概念来自于老式的汽车收音机里的机 械式按钮;当你按下一个按钮之后,另一个就会弹出来。因此你只能 选一个。要想创建一组相关联的 JRadioButton,只要把它们加进 ButtonGroup 就 可 以 了 ( 一 个 表 单 里 允 许 有 任 意 数 量 的 ButtonGroup)。如果你(用构造函数的第二个参数)把多个 radio button 设成 true 了,那么只有最后一个才是有效的。 注意, 捕捉 radio button 事件的方法同捕捉其它事件的完全相同。text field 它可以 可以代替 JLabel。 Combo boxes (下拉式列表) 同 radio button 组一样,下拉式列表只允许用户在一组选项里面选 取一个元素。但是这种做法更简洁,而且能在不惊扰客户的情况下修 改列表中的元素。(你也可以动态地修改 radio button,但是这个视 觉效果就太奇怪了)。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

JComboBox 的默认行为同 Windows 的 combo box 不太一样。 在 Windows 里,你既可以从 combo box 的列表里选一个,也可以自己输入,但是 在 JComboBox 里,这么做就必须先调用 setEditable( )了。此外你 只能从列表中选择一个。下面我们举一个例子。我们先给 JComboBox 加几个选项,然后每按一下按钮就加一个选项。 List boxes List box 与 JComboBox 的不同不仅仅在外观上,它们之间有着非常 重大的区别。JComboBox 激活之后会弹出下拉框而 JList 则总是会在 屏幕上占一块固定大小,永远也不会变。如果你想列表里的东西,只 要调用 getSelectedValues( )就行了,它会返回一个 String 的数组, 其 中 包 含 着 被 选 中 的 元 素 。 JList 能 做 多 项 选 择 ; 如 果 你 control-click 了一个以上的选项(在用鼠标进行选择的时候,一直 按 着 "control" 键 ) , 那 么 已 选 中 的 选 项 就 会 一 直 保 持 " 高 亮 (highlighted)",这样你能选任意多个了。如果你先选一项,然后再 shift-click 另一个,那么两个选项之间的所有选项就都被选中了。 要取消一个选项只要 control-click 就可以了。如果你只是想把 String 数组放到 JList 里面,那么还有一个更简单的办法;就是把 数组当作参数传给 JList 的构造函数,这样它就会自动创建一个列表 了。JList 不会自动提供滚动轴。当然只要把它嵌到 JScrollPane 里 它就会自动镶上滚动轴了,具体的细节它自会打理。 Tabbed panes JTabbedPane 能创建"带页签的对话框(tabbed dialog)",也就是对

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

话框的边上有一个像文件夹的页签一样的东西,你只要点击这个页 签,对话框就把这一页显示出来。在 Java 编程当中熟练使用页签面 板(tabbed panel)相当重要,因为每当 applet 要弹出一个对话框的 时候,它都会自动加上一段警告,所以弹出式对话框在 applet 里并 不受欢迎。程序运行起来你就会发现,如果 tab 太多了,JTabbedPane 还会自动把它们堆起来以适应一定的行宽。 Message boxes 图形界面系统通常都包含一套标准的,能让你迅速地将消息传给用 户,或者从用户那里得到信息的对话框。对于 Swing 来说,这些消息 框就包含在 JOptionPane 里面了。你有很多选择(有些还相当复杂), 但是最常用的可能还是"确认对话框(confirmation dialog)",它用 static

JOptionPane.showMessageDialog(

)



JOptionPane.showConfirmDialog( )启动。 注意 showOptionDialog( ) 和 showInputDialog( )会返回用户输入的信息。菜单所有能包含菜 单的组件,包括 JApplet,JFrame,JDialog 以及它们所派生的组件, 都有一个需要 JMenuBar 作参数的 setJMenuBar( )方法(一个组件只 能有一个 JMenuBar)。你可以把 JMenu 加入 JMenuBar,再把 JMenuItem 加入 JMenu。每个 JMenuItem 都可以连一个 ActionListener,当你选 中菜单项(menu item)的时候,事件就发出了。与使用资源的系统不 同,Java 和 Swing 要求你必须用源代码来组装菜单。通常情况下每 个 JMenuItem 都得有一个它自己的 ActionListener。JMenuItem 是 AbstractButton 的派生类,所以它有一些类似按钮的行为。 JMenuItem

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

本身就是一个可以置入下拉菜单的菜单选项。此外 JMenuItem 还有三 个派生类:用来持有其它 JMenuItem 的 JMenu(这样你就可以做层叠式 菜单了);有一个能表示是否被选中的"候选标记(checkmark)"的 JCheckBoxMenuItem ; 以 及 包 含 一 个

radio

button



JRadioButtonMenuItem。为了演示在程序运行期间动态地交换菜单 条,我们创建了两个 JMenuBar。你会发现 JMenuBar 是由 JMenu 组成 的,而 JMenu 又是由 JMenuItem,JCheckBoxMenuItem 甚至 JMenu(它 会 创 建 子 菜 单 ) 组 成 的 。 等 JMenuBar 组 装 完 毕 , 你 就 可 以 用 setJMenuBar( )方法把它安装到当前程序里面了。注意当你按下钮按 的时候,它会用 getJMenuBar( )来判断当前的菜单,然后把另一菜 单换上去。字符串的比较是引发编程错误的一大诱因。程序会自动的 将菜单项勾掉或恢复。JCheckBoxMenuItem 的代码演示了两种怎样决 定该勾掉哪个菜单项的方法。一个是匹配字符串(虽然也管用,但就 像上面我们所讲的,不是很安全),另一个则是匹配事件目标对象。 正如你所看到的,getState( )方法可以返回 JCheckBoxMenuItem 的 状态,而 setState( )则可以设置它的状态。菜单事件不是很一致, 这一点可能会引起混乱。JMenuItem 使用 ActionListener 事件,而 JCheckBoxMenuItem 则 使 用 ItemListener 事 件 。 JMenu 也 支 持 ActionListener,但通常没什么用。总之,你得为每个 JMenuItem, JCheckBoxMenuItem 或 JRadioButtonMenuItem 都制备一个 listener, 不过这里我们偷了点懒,把一个 ItemListener 和 ActionListener 连 到多个菜单组件上了。Swing 支持助记符,或者说"快捷键",这样你

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

就可以扔掉鼠标用键盘来选取 AbstractButton 了(按钮,菜单项等)。 要这么做很容易,就拿 JMenuItem 举例,你可以用它重载了的构造函 数,把快捷键的标识符当作第二个参数传给它。不过绝大多数 AbstractButton 都没有提供类似的构造函数,所以比较通用的办法 还是用 setMnemonic( )方法。在上述例程中我们为按钮和多个菜单 项加上了快捷键,这些快捷键的提示会自动显示在组件上。好工具应 该能帮你维护好菜单。弹出式菜单 要想实现 JPopupMenu,最直截了 当的办法就是创建一个继承 MouseAdapter 的内部类,然后把这内部 类的实例加到要提供弹出式菜单的组件里:JMenuItem 用的是同一个 ActionListener , 它 负 责 从 菜 单 标 签 里 面 提 文 本 并 且 把 它 插 入 JTextField。 画屏幕 一个好的 GUI 框架能让作图相对而言比较简单??确实如此,Swing 就 做到了。所有作图问题都面临同一个难点,那就是相比调用画图函数, 计算该在哪里画东西通常会更棘手,但不幸的是这种计算又常常和作 图函数的调用混在一起,所以作图函数的接口的实际的复杂程度很可 能会比你认为的要简单。虽然你可以在任何一个 JComponent 上作画, 也就是说它们都能充当画布(canvas),但是如果你想要一块能直接画 东西的白板,最好还是创建一个继承 JPanel 的类。这样你只需要覆 写一个方法,也就是 paintComponent( )就行了。当系统需要重画组 件的时候,会自动调用这个方法(通常情况下,你不必为此操心,因 为这是由 Swing 控制的)。调用的时候,Swing 会传一个 Graphics 对

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

象给这个方法,这样你就能用这个对象作画了。覆写 paintComponent( )的时候,必须首先调用其基类的同名方法。接下 来再做什么就由你了决定了;通常是调用 Graphics 的方法在 JPanel 上画画或者是在其象素上着色。要想了解具体怎样使用这些方法,可 以查阅 java.awt.Graphics 文档(可以到 java.sun.com 上面去找 JDK 文档)。如果问题非常复杂,那么还有一些更复杂的解决方案,比如 第三方的 JavaBean 或者 Java 2D API。 对话框 所谓对话框是指,能从其他窗口里面弹出来的窗口。其作用是,在不 搞乱原先窗口前提下,具体处理其某一部分的细节问题。对话框在 GUI 编程中的用途很广,但是在 applet 中用的不多。要想创建对话 框, 你得先继承 JDialog。和 JFrame 一样,JDialog 也是另一种 Window, 它也有布局管理器(默认情况下是 BorderLayout),也可以用事件监 听器来处理事件。它同 JFrame 有一个重要的区别,那就是在关对话 框的时候别把程序也关了。相反你得用 dispose( )方法将对话框窗 口占用的资源全部释放出来。一旦创建完 JDialog,你就得用 show( ) 来显示和激活它了。关闭对话框的时候,还得记住要 dispose( )。 你会发现,对 applet 来说,包括对话框在内所有弹出的东西都是" 不可信任的"。也就是说弹出来的窗口里面有一个警告。这是因为, 从理论上讲恶意代码可以利用这个功能来愚弄用户,让他们觉得自己 是在运行一个本地应用程序,然后误导它们输入自己的信用卡号码, 再通过 Web 传出去。applet 总是和网页联在一起,因此只能用浏览

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

器运行,但是对话框却可以脱离网页,所以从理论上讲这种欺骗手段 是成立的。所以这么一来,applet 就不太会用到对话框了。由于 static 只能用于宿主类,因此内部类里不能再有 static 的数据或是 嵌套类了。paintComponent( )负责把 panel 的周围的方框以及"x" 或"o"画出来。虽然充斥着单调的计算,但是还算简明。 文件对话框 有些操作系统还内置了一些特殊的对话框,比如让你选择字体,颜色, 打印机之类的对话框。实际上所有的图形操作系统都提供了打开和存 储文件的对话框,所以为了简化起鉴,Java 把它们都封装到 JFileChooser 里面了。 注意 JFileChooser 有很多变例可供选择,比方说加一个过滤器滤文 件名之类的。要用"open file"对话框就调用 showOpenDialog( ), 要用"save file"对话框,就调用 showSaveDialog( )。在对话框关 闭之前,这两个函数是不会返回的。即便对话框关了,JFileChooser 对象仍然还在,所以你还去读它的数据。要想知道操作的结果,可以 用 getSelectedFile( )和 getCurrentDirectory( )。如果返回 null 则说明用户按了 cancel。Swing 组件上的 HTML 所有能显示文件的组 件都可以按照 HTML 的规则显示 HTML 的文本。也就是说你可以很方便 地让 Swing 组件显示很炫的文本。比如:文本必须以""开头, 下面你就可以用普通的 HTML 标记了。注意,它没有强制你一定要关 闭标记。JTabbedPane,JMenuItem,JToolTip,JRadioButton 以及 JCheckBox 都支持 HTML 文本。Slider 和进程条 Slider 能让用户通过

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

来回移动一个点来输入数据,有时这种做法还是很直观的(比方说调 节音量)。进程条(progress bar)则以一种用类比的方式显示数据, 它表示数据是"全部"还是"空的",这样用户就能有一个比较全面的了 解了。JProgressBar 还比较简单,而 JSlider 的选项就比较多了, 比如摆放的方向,大小刻度等等。注意一下给 Slider 加带抬头的边 框的那行代码,多简洁。 树 JTree 的用法可以简单到只有下面这行代码: add(new JTree(new Object[] {"this", "that", "other"})); 这样显示的是一棵最基本的树。JTree 的 API 非常庞大,应该是 Swing 类库里最大的之一了。虽然你可以用它来做任何事情,但是要想完成 比较复杂任务,就需要一定的研究和实验了。好在这个类库还提供了 变通手段,也就是一个"默认"的,能满足一般需求的树型组件。所以 绝大多数情况下你都可以使用这个组件,只有在特殊情况下,你才需 要去深入研究树。Trees 类包含一个用来创建多个 Branch 的两维 String 数组,以及一个用来给数组定位的 static int i。节点放在 DefaultMutableTreeNode 里面,但是实际在屏幕上显示则是由 JTree 及与之相的 model??DefaultTreeModel 控制的。注意 JTree 在加入 applet 之前,先套了一件 JScrollPane,这样它就能提供自动的滚动 轴了。对 JTree 的操控是经由它的 model 来实现的。当 model 发生变 化时,它会产生一个事件,让 JTree 对树的显示作出必要的更新。 init( )用 getModel( )提取这个 model。当你按下按钮的时候,它会 创建一个新的新的"branch" 。等它找到当前选中的那个节点(如果什

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

么也没选,就用根节点)之后,model 的 insertNodeInto( )方法就会 接管所有的任务了,包括修改树,刷新显示等等。或许上述例程已能 满足你的需求了。但是树的功能强大到只要你能想到它就能做到的地 步。但是要知道:差不多每个类都有一个非常庞大的接口,所以你要 花很多时间和精力去理解它的内部构造。但话说回来,它的设计还是 很优秀的,其竞争者往往更糟。 表格 和树一样,Swing 的表格控件也非常复杂强大。刚开始的时候,他们 是想把它做成用 JDBC 连接数据库时常用的"grid"接口,因此它具有 极高的灵活性,不过代价就是复杂度了。它能让你轻易创建一个全功 能的电子表格程序,不过这要花整整一本书篇幅才能讲清楚。但是如 果你弄懂了基本原理,也可以用它来创建一个相对简单的 JTable。 JTable 只负责怎样显示数据,而数据本身是由 TableModel 控制的。 所以在创建 JTable 之前,你通常都得先创建一个 TableModel。你可 以 从 头 开 始 去 实 现 TableModel 接 口 , 但 是 Java 提 供 了 一 个 AbstractTableModel 的帮助类,继承它会比较简单。选择 Look & Feel 所谓"可插接式的外观风格(look & feel)"是指,你可以让程序模拟 其他操作环境的外观。你甚至可以做一些更炫的事情,比如在程序运 行期间动态改变其外观。但是通常情况下,你只会在下面两项中选一 个:选择"跨平台"的外观(也就是 Swing 的"metal"),或者选当前操作 系统的外观,让 Java 程序看上去就像是为这个操作系统定制的(绝大 多数情况下,这几乎是勿庸置疑的选择,这样用户就不至于被搞糊涂

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

了)。不管你选哪个,代码都很简单,但是必须先执行这些代码再创 建组件,因为组件是按照当前的 look and feel 创建的,而且程序运 行到一半的时候,你再去改 look and feel,它就不会跟着你去改了。 (这个过程非常复杂,而且并不实用,所以我们把它留给 Swing 专著 了)。实际上如果你认为跨平台的("metal")外观是 Swing 程序的特 色,而你也想用它,那你就可以什么都不作了??它是默认的 look and feel。但是如果你选择当前操作系统的外观风格,那么只要插入下面 这段代码就可以了,一般来说是放在 main( )开头的地方,但是最晚 要在加第一个组件之前: try { UIManager.setLookAndFeel(UIManager. getSystemLookAndFeelClassName()); } catch(Exception e) { throw new RuntimeException(e); } 你根本不用在 catch 里面做任何事, 因为如果选择其他 look and feel 失败的话,UIManager 会回到默认的跨平台的 look and feel。但是 在调试的时候这个异常还是很起作用的,最起码你可以从 catch 里看 到些什么。下面是一个用命令行参数来选择 look and feel 的程序, 顺便也看看这几个组件在不同的 look and feel 下都是什么样子:假 如你为一个对程序外观有特殊要求的公司做一个 framework 的话,你 甚至可以自创一套 look and feel。不过这可是一个大工程,其难度

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

远远超出了本书的范围。 剪贴板 JFC 与系统剪贴板的互动功能非常有限(在 java.awt.datatransfer package 里面)。你可以把 String 对象当作文本复制到剪贴板里,也 可以把剪贴板里的文本粘贴到 String 对象里。当然剪贴板支持任何 类型的数据,至于数据在剪贴板里该怎么表示,那是粘贴数据的程序 的事。Java 通过"flavor"这个概念加强了剪贴板 API 的扩展性。当 数据进到剪贴板的时候还跟着一组与这个数据相关联的,可以转换这 些数据的 flavor(比方说一幅画可以表示成一个全部有数字组成的字 符串或一个 image),这样你就能知道剪贴板里的数据是否支持你感 兴趣的 flavor 了。JTextField 和 JTextArea 它们原本就已经支持剪 贴板了。可以期待,未来 Java 会提供更多的 flavor。你能得到更多 的数据 flavor 的支持。将 applet 打成 JAR 卷宗 JAR 的一个主要用途 就是优化 applet 的装载。在 Java 1.0 时代, 程序员们都尽量把 applet 的代码塞进一个类里,这样当用户下载 applet 的时候只需向服务器 发一次请求就可以了。但这么做不仅使代码变得非常难读(也难维 护),而且.class 文件也是未经压缩的,因此下载速度仍有提升的潜 力。JAR 解决了这个问题,它把所有的.class 文件全都压缩在一个供 浏览器下载的文件里。现在你可以大胆运用正确的设计方案而不用再 担心它会产生多少.class 文件了,而用户的下载速度也更快了。签 发 applet 由于沙箱安全模型的限制,未获签名的 applet 是不能在客 户端进行某些操作的,比如写文件,连接本地网络等。一旦你签发了

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

applet,用户就可以去核对那个自称创建了这个 applet 的人是不是 真的就是创建者了,同时他们也可以确认 JAR 文件是不是在离开服务 器之后被篡改了。没有这些最起码的保证,applet 是根本不可能去 做任何可能损坏计算机或泄漏个人隐私的事的。 这层限制对于 applet 在 Internet 上的安全运用是至关重要的,但同时也削弱了 applet 的 功能。自从有了 Java Plugin,签发 applet 的步骤也变得更简单也 更标准化了,而 applet 也成为一种更简便的部署应用程序的方法了。 签发 applet 已经变得非常简单了,而且也有了标准的 Java 工具了。 早先 plugin 还没出来的时候,你得用 Netscape 的工具为 Netscape 的用户签署.jar 文件,用 Microsoft 的工具为 Internet Explorer 用户签署.cab 文件,然后在 HTML 文件里面为两个平台各自准备一套 标记。而用户也必须在浏览器里安装证书,这样 applet 才能获得信 任。Plugin 不仅提供了标准化的签署和部署 applet 的方法,而且能 自动安装证书,方便了用户。 } ///:~ 要想签名,你必须先把它做成一个 JAR 文件(见本章前面讲过的 jar 工具这一节),然后再签署这个文件。有了 JAR 文件,你就得用证书 或是密钥来签名了。如果是一个大公司,那么你可以跟 Verisign 或 Thawte 这样的"认证中心(signing authority)"提申请,它们会给你 发给你证书的。证书是用来给代码签名的,这样用户就能确信你确实 是他所下载的这段代码的提供者了,而且自你签发之后,这段代码未 被篡改过。电子签名的本质是一串两进制的数,当有人要核对签名的

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

时候,那个给你发证书的认证中心会为你作证。认证中心发的证书是 要付钱的,而且得定期更新。就这个问题而言,我们可以自己给自己 签一张证书。这个证书会存在文件里(通常被称为 keychain)。你可 以用下面这条命令: keytool ?list 访问默认的文件。如果默认的文件不存在,那么你还得先建一个,或 者告诉它去找哪个文件。或许你应该去试试"cacerts"文件。 keytool -list -file <path/filename> 其默认路径通常是 {java.home}/lib/security/cacerts 其中,java.home 表示 JRE 所在的目录。 你也可以用 keytool 给自己发一份证书,供测试用。如果 PATH 环境 变量里已经有 Java 的"bin"目录了,那么这个命令就是: keytool ?genkey ?alias -keystore 其中 keyname 表示 key 的别名,比如“mykeyname”,url 表示存放密 钥的位置,通常就放在上面讲的 cacerts 文件里。它会提示你输入 (keystore 的)密码。默认是"changeit"(提醒你该做些什么)。然后 是姓名,部门,单位,城市,州,国家。这些信息会被放进证书里。 最后它会要你给证书设一个密码。如果你对安全问题真的很在意,可 以给它设一个单独的密码,默认情况下,证书的密码就是"存证书的 文件(keystore)"的密码,一般来说这已经够了。上面这些信息还可 以用命令行提供给像 Ant 这样的编译工具使用。如果你不给参数,直

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

接在命令行下用 keytool 命令,那么它会把所有的选项全部都打印出 来。你或许想用-valid 选项,看看证书的有效期还有多长。如果想 确认证书确实保存在 cacerts 文件里,用 keytool ?list ?keystore 然后输入前面设的密码。或许你的证书和别人的存放在一起(如果别 人已经在这个 keystore 里存了证书的话)。你刚获得的那张证书是你 自己签发的,所以认证中心是不会认帐的。如果你用这张证书签发 JAR 文件,最终用户那里就会看到一个警告窗口,同时强烈建议他们 不要使用这个程序。除非你去买一份有效力的证书,否则否则你和你 的用户就得忍着。签发 JAR 文件要用 Java 的 jarsigner 标准工具, 命令如下: jarsigner ?keystore <jarfile> url 表示 cacerts 文件的位置,jarfile 表示 JAR 文件的名字,而 keyname 则是证书的别名。你还得再输一遍密码。现在这个 JAR 文件 就带上你的这张证书的签名了,而用户也能知道它在签发之后是不是 被篡改了(包括修改,添加或删除等)。接下来你得操心一下 HTML 文 件的 applet 标记的"archive"属性了,JAR 的文件名就在这里。如果 浏览器用的是 Java 的 plugin,applet 的标记还要更复杂一些,不过 你可以创建一个简单点的,就像这样: <APPLET CODE=package.AppletSubclass.class

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

ARCHIVE = myjar.jar WIDTH=300 HEIGHT=200>
然后用 HTMLConverter 过一遍,它会自动帮你生成正确的 applet 标 记。现在当用户下载 applet 时,浏览器就会提醒他们现在正在装载 的是一个带签名的 applet,并且问他是不是信任这个签发者。正如 我们前面所讲的,测试用的证书并不具备很高的可信度,因此它会给 一个警告。如果客户信任了,applet 就能访问整个客户系统了,于 是它就和普通的程序没什么两样了。JNLP 和 Java Web Start 虽然经 过签名的 applet 功能强大,甚至能在有效地取代应用程序,但它还 是得在 Web 浏览器上运行。这不仅使客户端增加了额外的运行浏览器 的开销,而且常常使用户界面变得非常的单调和混乱。浏览器有它自 己的菜单和工具条,而他们正好压在 applet 的上面。Java 的网络启 动协议(Java Network Launch Protocol 简称 JNLP)能在不牺牲 applet 优点的前提下解决这个问题。你可以在客户端上下载并安装单独的 JNLP 应用程序。它可以用命令行,桌面图标,或随 JNLP 一同分发的 应用程序管理器启动。程序甚至可以从最初下载的那个网站上启动。 JNLP 程序运行的时候会动态地从 Internet 上下载资源,并且自动检 查其版本(如果用户连着 Internet 的话)。也就是说它同时具备 applet 和 application 的优点。和 applet 一样,客户机在对待 JNLP 应用程序的时候也必须注意安全问题。JNLP 应用程序是一种易于下

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

载的,基于 Web 的应用程序,因此有可能会被恶意利用。有鉴于此, JNLP 应用程序应该和 applet 一样被放在沙箱里。同 applet 一样, 它可以用带签名的 JAR 文件部署,这时用户可以选择是不是信任签发 者。和 applet 的不同之处在于,即便没有签名,它仍然可以通过 JNLP API 去访问客户系统的某些资源(这就需要用户在程序运行时认可这 些请求了)。JNLP 是一个协议而非产品,因而得先把它实现了才能用。 Java Web Start 有称 JAWS 就是 Sun 提供的,能免费下载的,JNLP 的 官方样板实现。你只要下载安装就行了,如果要做开发,不要忘了把 JAR 文件放到 classpath 里面。要想在网站上部署 JNLP 应用程序, 只要确保服务器能认得 application/x-java-jnlp-file 的 MIME 类型 就 行 了 。 如 果 是 用 最 新 版 的

Tomcat

服 务 器

(http://jakarta.apache.org/tomcat),那它应该已经帮你配置好 了。否则就去查查服务器的用户手册。创建 JNLP 应用程序并不难。 先创建一个标准的应用程序,然后用 JAR 打包,最后再准备一个启动 文件就行了。启动文件是一个很简单的 XML 文件,它负责向客户端传 递下载和安装应用程序的信息。如果你决定用不带签名的 JAR 文件来 部署软件,那还得用 JNLP API 来访问客户端系统上的资源。注意, FileOpenService 和 FileCloseService 是 javax.jnlp 里的类,要使 用这两个服务,不但要用 ServiceManager.lookup( )提出请求,而 且要用这个方法所返回的对象来访问客户端资源。如果你不想受 JNLP 束缚,要直接使用这些类的话,那就必须使用签名的 JAR 文件。这个 启动文件的后缀名必须是.jnlp,此外它还必须和 JAR 文件呆在一个

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

目录里。这是一个根节点为<jnlp>标记的 XML 文件。这个节点下面还 包括了一些子元素,其中绝大部分是自解释的。jnlp 元素的 spec 属 性告诉客户端系统,这个应用程序需要哪个版本的 JNLP。codebase 属性告诉客户端到哪个目录去找启动文件和资源。通常它应该是一个 指向 Web 服务器的 HTTP URL,但这里为了测试需要,我们把它指到 本机的目录了。href 属性表示文件的名字。information 标记里有多 个提供与程序相关的信息的子元素。它们是供 Java Web Start 的管 理控制台或其它类似程序使用的。这些程序会把 JNLP 应用安装到客 户端上,让后让用户通过命令行,快捷方式或者其它什么方法启动。 resource 标记的功能 HTML 文件里的 applet 标记相似。J2SE 子元素 指明程序运行所需的 j2se 的版本,jar 子元素告诉客户端 class 文 件被打在哪个 JAR 文件里。此外 jar 元素还有一个 download 属性, 其值可以是"eager"或"lazy",它的作用是告诉 JNLP 是不是应该下载 完这个 jar 再开始运行程序。application-desc 属性告诉客户端系 统,可执行的 class,也就是 JAR 文件的入口是哪个类。jnlp 标记还 有一个很有用的子元素,那就是这里没用到的 security 标记。下面 我们来看看 security 标记长什么样子: <security> <security/> 只有在部署带签名的 JAR 文件时才能使用 security 标记。上面那段 程序不需要这个标记,因为所有的本地资源都是通过 JNLP 服务来访

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

问的。此外还有一些其它标记,具体细节可以参考 http://java.sun.com/products/javawebstart/download-spec.htm 现在.jnlp 文件也写好了,接下来就是在网页里加超链接了。这个页 面应该是个下载页面。页面上除了有复杂的格式和详细介之外,千万 别忘了把这条加上: click here 这样你就可以点击链接启动 JNLP 应用程序的安装进程了。你只要下 载一次,以后就可以通过管理控制台来进行配置了。如果你用的是 Windows 的 Java Web Start 的话,那么第二次启动程序的时候,它 会提示你,是不是创建一个快捷方式。这种东西是可以配置的。我们 这里只介绍了两个 JNLP 服务,而当前版本里有七种。它们都是为特 定的任务所设计的,比如像打印,剪贴板操作等。 编程技巧 由于 Java 的 GUI 编程是一个还在不断改进的技术,Java 1.0/1.1 与 Java 2 的 Swing 类库之间就有着非常重大的区别,与旧模式相比, Swing 能让你用一种更好的方式编程。这里,我们会就其中一些问题 做个介绍,同时检验一下这些编程技巧。 动态绑定事件 Swing 的事件模型的优点就在于它的灵活性。你可以调用方法给组件 添加或删除事件。Button 可以连不止一个 listener。通常组件是以 多播(multicast)方式处理事件的,也就是说你可以为一

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

个事件注册多个 listener。但是对于一些特殊的,以单播(unicast) 方式处理事件的组件,这么做就会引发 TooManyListenersException 了。 程序运行的时候能动态地往 Button b2 上面添加或删除 listener。 你应该已经知道加 listener 的方法了,此外每个组件还有一个能用 来删 listener 的 removeXXXListener( )方法。 这种灵活性为你带来更大的便利值得注意的是,listener 的添加顺 序并不一定就是它们的调用顺序(虽然绝大多数 JVM 确实是这么实现 的)。将业务逻辑(business logic)与用户界面分离开来一般情况下, 设计类的时候总是强调一个类"只作一件事情"。涉及用户界面的时候 更是如此,因为你很可能会把"要作什么"同"要怎样显示"给混在一起 了。这种耦合严重妨碍了代码的复用。比较好的做法是将"业务逻辑 (business login)"同 GUI 分离开来。这样不仅方便了业务逻辑代码 的复用, 也简 化了 GUI 的 复用。 还 有一种情 况, 就是多 层系统 (multitiered systems),也就是说”业务对象(business object)" 完全贮存在另一台机器上。业务规则的集中管理能使规则的更新立即 对新交易生效,因此这是这类系统所追求的目标。但是很多应用程序 都会用到这些业务对象,所以它们绝不能同特定的显示模式连在一 起。它们应该只做业务处理,别的什么都不管。树立了将 UI 同业务 逻辑相分离的观点之后,当你再碰到用 Java 去维护遗留下来的老代 码时,也能稍微轻松一点。 范式内部类,Swing 事件模型,还能继续用下去的 AWT 事件模型,以 及那些要我们用老办法用的新类库的功能,所有这些都使程序设计变

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

得更混乱了。现在就连大家写乱七八糟的代码的方式也变得五花八门 了。这些情况都是事实,但是你应该总是使用最简单也最有条理的解 决方案:用 Listener(通常要写成内部类)来处理事件。 用了这个模型, 你可以少写很多"让我想想这个事件是谁发出的"这种代码。所有代码 都在解决问题,而不是在做类型检查。这是最佳的编程风格,写出来 的代码不仅便于总结,可读性和可维护性也高。 并发与 Swing 写 Swing 程序的时候,你很可能会忘了它还正用着线程。虽然你并没有明确地 创建过 Thread 对象,但它所引发的问题却会乘你不备吓你一跳。绝 大多数情况下,你写的 Swing 或其他带窗口显示的 GUI 程序都是事件 驱动的,而且除非用户用鼠标或键盘点击 GUI 组件,否则什么事都不 会发生。只要记住 Swing 有一个事件分派线程就行了,它会一直运行 下去,并且按顺序处理 Swing 的事件。如果你想确保程序不会发生死 锁或者竞争的情形,那么倒是要考虑一下这个问题。 重访 Runnable 在第 13 章,我曾建议大家在实现 Runnable 接口时一定要慎重。 当 然如果你设计的类必须继承另一个类而这个类又得有线程的行为,那 么选择 Runnable 还是对的。不同的 JVM,在如何实现线程方面,存 在着巨大的性能和行为差异。管理并发当你用 main 方法或另一个线 程修改 Swing 组件的属性时,一定要记住,有可能事件分派线程正在 和你竞争同一个资源。看来线程遇到 Swing 的时候,麻烦也跟着来了。 要解决这个问题,你必须确保 Swing 组件的属性只能由事件分派线程 来 修 改 。 这 要 比 听 上 去 的 容 易 一 些 。 Swing 提 供 了 两 个 方 法 , SwingUtilities.invokeLater(

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

)



SwingUtilities.invokeandWait( ),你可以从中选一个。它们负责 绝大多数的工作,也就是说你不用去操心那些很复杂的线程同步的事 了。这两个方法都需要 runnable 对象作参数。当 Swing 的事件处理 线程处理完队列里的所有待处理事件之后,就会启动它的 run( )方 法了。能用这两个方法来设置 Swing 组件的属性。 可视化编程与 JavaBeans 看到现在你已经知道 Java 在代码复用方面的价值了。复用程度最高 的代码是类,因为它是由一组紧密相关的特征(字段 field)和行为(方 法)组成的,它既能以合成(composition),也能以继承的方式被复用。 继承和多态是面向对象编程的基础,但是在构建应用程序的时候,绝 大多数情况下,你真正需要的是能帮你完成特定任务的组件。你希望 能把这些组件用到设计里面,就像电子工程师把芯片插到电路板上一 样。同样,也应该有一些能加速这种"模块化安装"的编程方法。 Microsoft 的 Visual Basic 为"可视化编程(Visual programming)" 赢得了初次成功??非常巨大的成功,紧接着是第二代的 Borland Delphi(直接启发了 JavaBean 的设计) 。有了这些工具,组件就变得 看得见摸的着了,而组件通常都表示像按钮,文本框之类的可视组件, 因此这样一来组件编程也变得有意义了。实际上组件的外观,通常是 设计时是什么样子运行时也就这样,所以从控件框(palette)里往表 单上拖放组件也就成了可视化编程的步骤了。而当你在这么做的时 候,应用程序构造工具在帮你写代码,所以当程序运行时,它就会创 建那些组件了。通常简单地把组件拖到表单上还不足以创建程序。你

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

还得修改一些特征,比如它的颜色,上面的文字,所连接的数据库等 等。这些在设计时可以修改的特征被称为属性(properties)。你可以 在应用程序的构建工具里控制组件的属性。当程序创建完毕,这些配 置信息也被保存下来,这样程序运行时就能激活这些配置信息了。看 到现在你或许已经习惯这样来理解对象了,也就是对象不仅是一组特 征,还是一组行为。设计的时候,可视组件的行为部分的表现为事件, 也就是说"是些能发生在这个组件上的事情"。一般来说你会用把代码 连到事件的方法来决定事件发生时该做些什么。下面就是关键部分了: 应用程序的构建工具用 reflection 动态地查询组件,找出这个组件 支持哪些属性和事件。一旦知道它是谁,构建工具就能把这些属性显 示出来,然后让你作修改了(创建程序的时候会把这些状态保存下 来),当然还有事件。总之,只要你在事件上双击鼠标或者其他什么 操作,编程工具就会帮你准备好代码的框架,然后连上事件。现在, 你只要编写事件发生时该执行的代码就可以了。编程工具帮你做了这 么多事,这样你就能集中精力去解决程序的外观和功能问题了,至于 把各部分衔接起来的细节问题,就交给构建工具吧。可视化编程工具 之所以能获得如此巨大的成功,是因为它能极大的提高编程的效率, 当然这一点首先体现在用户界面,但是其它方面往往也受益颇丰。 JavaBean 是干什么用的? 言归正传,组件实际上是一段封装成类的代码。关键在于,它能让应 用程序的构建工具把自己的属性和事件提取出来。创建 VB 组件的时 候,程序员必须按照特定的约定,用相当复杂的代码把属性和事件发

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

掘出来。Delphi 是第二代的可视化编程工具,而且整个语言是围绕 着可视化编程设计的,所以用它创建可视化组件要简单得多。但是 Java 凭借其 JavaBean 在可视化组件的创建技术领域领先群雄。Bean 只是一个类,所以你不用为创建一个 Bean 而去编写任何额外的代码, 也不用去使用特殊的语言扩展。事实上你所要做的只是稍稍改变一下 方法命名的习惯。是方法的名字告诉应用程序构建工具,这是一个属 性 , 事 件 还 是 一 个 普 通 的 方 法 。 JDK 文 档 把 命 名 规 范 (naming convention)错误地表述成"设计模式(design pattern)” 。这真是不 幸 , 设 计 模 式 ( 请 参 阅 www.BruceEckel.com 上 的 Thinking in Patterns (with Java))本身已经够让人伤脑筋的了,居然还有人来 混淆视听。重申一遍,这算不上是什么设计模式,只是命名规范而已, 而且还相当简单。对于名为 xxx 的属性,你通常都得创建两个方 法:getXxx( )和 setXxx( )。注意构建工具会自动地将"get"和"set" 后面的第一个字母转换成小写,以获取属性的名字。"get"所返回的 类型与”set"所使用的参数的类型相同。属性的名字同"get"和”set" 方法返回的类型无关。对于 boolean 型的属性,你既可以使用上述的 "get"和"set"方法,也可以用"is"来代替"get"。Bean 的常规方法无 需遵循上述命名规范,但它们都必须是 public 的。用 Swing 的 listener 来处理事件。就是我们讲到现在一直在用的这个方案:用 addBounceListener(BounceListener)



removeBounceListener(BounceListener)来处理 BounceListener。 绝大多数情况下,内置的事件和监听器已经可以满足你的需要了,但

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

是你也可以创建你自己的事件和监听器接口。第一点回答了你在比较 新旧代码时或许会注意的一个问题:很多方法的名字都有了一些很小 的,但明显没什么意义的变化。现在你应该知道了,为了把组件做成 JavaBean,绝大多数修改是在同"get"和"set"的命名规范接轨。用 Introspector 提取 BeanInfo 当你把 Bean 从控件框(palette)里拖到 表单上的时候,JavaBean 架构中最重要的一环就开始工作了。应用 程序构建工具必须能创建这个 Bean(有默认构造函数的话就可以了), 然后在不看 Bean 源代码的情况下提取所有必须的信息,然后创建属 性表和事件句柄。从第十章看,我们已经能部分地解决这个问题了: Java 的 reflection 机制可以帮我们找出类的所有方法。我们不希望 像别的可视化编程语言那样用特殊的关键字来解决 JavaBean 的问 题,因此这是个完美的解决方案。实际上给 Java 加上 reflection 的 主要原因,就是为了支持 JavaBean(虽然也是为了支持"对象的序列 化 (object serializaiton)" 和 " 远 程 方 法 调 用 (remote method invocation)"。所以也许你会想设计应用程序构建工具的人会逐个地 reflect Bean,找出所有的方法,再在里面挑出 Bean 的属性和事件。 这么做当然也可以,但是 Java 为我们提供了一个标准的工具。这不 仅使 Bean 的使用变得更方便了,而且也为我们创建更复杂的 Bean 指 出了一条标准通道。这个工具就是 Introspector,其中最重要的方 法就是 static getBeanInfo( )。当你给这个方法传一个 Class 对象 时,它会彻底盘查这个类,然后返回一个 BeanInfo 对象,这样你就 可以通过这个对象找出 Bean 的属性,方法和事件了。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

通常你根本不用为此操心;绝大多数 Bean 都是从供应商那里直接买 过来的,更何况你也不必知道它在底层都玩了什么花样。你只要直接 把 Bean 放到表单上,然后配置一下属性,再写个程序处理一下事件 就可以了。 一个更复杂的 Bean 所有的字段都是 private 的这是 Bean 的通常做法??也就是说做成" 属性"之后,通常只能用方法来访问了。 JavaBeans 和同步 只要你创建了 Bean,你就得保证它能在多线程环境下正常工作,这 就是说:只要允许,所有 Bean 的 public 方法都必须是 synchronized。 当然这会影响性能(不过在最新版本的 JDK 里,这种影响已经明显下 降了)。如果性能下降确实是个问题,那么你可以把那些不致于引起 问题的方法的 synchronized 给去掉,但是要记住,会不会引发问题 不是一眼就能看出来的。这种方法首先是要小(就像上面那段程序里 的 getCircleSize( )),而且/或是"原子操作",就是说这个方法所 调用的代码如此之少,以至于执行期间对象不会被修改了。所以把这 种方法做成非 synchronized 的,也不会对性能产生什么重大影响。 所以你应该把 Bean 的所有 public 方法都做成 synchronized,只有 在有绝对必要,而且确实对性能提高有效的时候,才能把 synchronized 移 掉 。 当 你 将 多 播 事 件 发 送 给 一 队 对 此 感 兴 趣 的 listener 时,必须做好准备,listener 会随时加入或从队列中删除。 第一个问题很好解决,但第二个问题就要好好想想了。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

paintComponent( )也没有 synchronized。决定覆写方法的时候是不 是该加 synchronized 不像决定自己写的方法那样清楚。。这里,好像 paintComponent( )加不加 synchronized 一样都能工作。但必须考虑 的问题有:这个方法是否会修改对象的"关键"变量?变量是否”关键 "的判断标准是,它们是否会被其它线程所读写。(这里,读写实际上 都是由 synchronized 方法来完成的,所以你只要看这一点就可以了) 在这段程序里,paintComponent( )没有修改任何东西。 这个方法是否与这种"关键"变量的状态有关?如果有一个 synchronized 方法修改了这个方法要用到的变量,那么最好是把这 个方法也作成 synchronized 的。基于这点,你或许会发现 cSize 是 由 synchronized 方法修改的,因此 paintComponent( )也应该是 synchronized。但是这里你应该问问"如果在执行 paintComponent( ) 的时候,cSize 被修改了,最糟糕的情况是什么呢?"如果问题并不 严重,而且转瞬即逝的,那么为了防止 synchronized 所造成的性能 下降,你完全可以把 paintComponent( )做成非 synchronized 的。 第三个思路是看基类的 paintComponent( )是不是 synchronized,答 案是"否"。这不是一个万无一失的判断标准,只是一个思路。就拿上 面 那 段 程 序 说 吧 , paintComponent( ) 里 面 混 了 一 个 通 过 synchronized 方法修改的 cSize 字段,所以情况也改变了。但是请 注 意 , synchronized 不 会 继 承 ; 也 就 是 说 派 生 类 覆 写 的 基 类 synchronized 方法不会自动成为 synchronized 方法。paint( )和 paintComponent( )是那种执行得越快越好的方法。任何能够提升性

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

能的做法都是值得大力推荐的,所以如果你发觉不得不对这些方法用 synchronized,那么很有可能是一个设计失败的信号。 main( )的测试代码是根据 BangBeanTest 修改而得的。为了演示 BangBean2 的多播功能,它多加了几个监听器。 封装 Bean 要想在可视化编程工具里面用 JavaBean,必须先把它放入标准的 Bean 容器里。也就是把所有 Bean 的 class 文件以及一份申明"这是 一个 Bean"的"manifest"文件打成一个 JAR 的包。manifest 文件是一 种有一定格式要求的文本文件。对于 BangBean,它的 manifest 文件 是这样的: Manifest-Version: 1.0 Name: bangbean/BangBean.class Java-Bean: True 第一行表明 manifest 的版本,除非 Sun 今后发通知,否则就是 1.0。 第二行(空行忽略不计)特别提到了 BangBean.class 文件,而第三行 的意思是"这是 y 一个 Bean"。没有第三行,应用程序构建工具不会 把它看成 Bean。唯一能玩点花样的地方是,你必须在"Name:"里指明 正确的路径。如果你翻到前面去看 BangBean.java,就会发觉它属于 bangbean package( 因 此 必 须 放 到 classpath 的 某 个 目 录 的 "bangbean"的子目录里),而 manifest 的 name 也必须包含这个 package 的信息。此外还必须将 manifest 文件放到 package 路径的 根目录的上一层目录里,这里就是将 manifest 放到"bangbean"子目

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

录的上一层目录里。然后在存放 manifest 文件的目录里打入下面这 条 jar 命令:jar cfm BangBean.jar BangBean.mf bangbean 这里假 定 JAR 文件的名字是 BangBean.jar,而 manifest 文件的名字是 BangBean.mf。或许你会觉得有些奇怪,"我编译 BangBean.java 的时 候还生成了一些别的 class 文件,它们都放到哪里去了?"是的,它 们最后都放在 bangbean 子目录里,而上面那条 jar 命令的最后一个 参数就是 bangbean。当你给 jar 一个子目录做参数时,它会将整个 子目录都打进 JAR 文件里(这里还包括 BangBean.java 的源代码??你 自己写 Bean 的时候大概不会想把源代码打进包吧)。此外如果你把刚 做好的 JAR 文件解开,就会发现你刚写的那个 manifest 已经不在里 面了,取而代之的 是 jar 自 己生成 的(大致根据你写的) ,名为 MANIFEST.MF 的 manifest 文件,而且它把它放在 META-INF 子目录里 (意思是“meta-information”)。如果你打开这个 manifest 文件, 就会发现 jar 给每个文件加了条签名的信息,就像这样: Digest-Algorithms: SHA MD5 SHA-Digest: pDpEAG9NaeCx8aFtqPI4udSX/O0= MD5-Digest: O4NcS1hE3Smnzlp2hj6qeg== 总之,你不必为这些事担心。你作修改的时候可以只改你自己写的 manifest 文件,然后重新运行一遍 jar,让它来创建新的 JAR 文件。 你也可以往 JAR 文件里加新的 Bean,只是要把它们的信息加到 manifest 里面就行了。值得注意的是,你应该为每个 Bean 创建一个 子目录。这是因为当你创建 JAR 文件的时候,你会把子目录的

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

名字交给 jar,而 jar 又会把子目录里的所有东西都放进 JAR。所以 Frog 和 BangBean 都有它们自己的子目录。 等你把 Bean 封装成 JAR 文件之后,你就能把它们用到支持 Bean 的 IDE 里了。这个步骤会随开发工具的不同有一些差别,不过 Sun 在他 们的"Bean Builder"里提供了一个免费的 JavaBean 的测试床(可以到 java.sun.com/beans 去下载)。要把 Bean 加入 Bean Builer,只要把 JAR 文件拷贝到正确的目录里就行了。 Bean 的高级功能 你已经知道做一个 Bean 有多简单了,但是它的功能并不仅限于此。 JavaBean 的架构能让你很快上手,但是经过扩展,它也可以适应更 复杂的情况。这些用途已经超出了本书的范围,但是我会做一个简单 的介绍。你可以在 java.sun.com/beans 上找到更多的细节。属性是 一个能加强的地方。在我们举的例子里,属性都是单个的,但是你也 可以用一个数组来表示多个属性。这被称为索引化的属性(indexed property)。你只要给出正确的方法(还是要遵循方法的命名规范), Introspector 就能找到索引化的属性,这样应用程序构建工具就能 作出正确的反映了。属性可以被绑定,也就是说它们能通过 PropertyChangeEvent 通知其它对象。而其它对象能根据 Bean 的变 化,修改自己的状态。属性是可以被限制的,也就是说如果其他对象 认为属性的这个变化是不可接受的,那么它们可以否决这个变化。 Bean 用 PropertyChangeEvent 通 知 其 他 对 象 , 而 其 他 对 象 则 用 PropertyVetoException 来表示反对,并且命令它将属性的值恢复到

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

原来的状态。你也可以修改 Bean 在设计时的表示方式: 你可以为 Bean 提供自定义的属性清单。当用户选择其它 Bean 的时候,构建工 具会提供普通属性清单,但是当他们选用你的 Bean 时,它会提供你 定义的清单。你可以为属性创建一个自定义的编辑器,这样虽然构建 工具用的是普通的属性清单,但当用户要编辑这个特殊属性时,编辑 器就会自动启动了。 你可以为 Bean 提供一个自定义的 BeanInfo 类, 它返回的信息,可以同 Introspector 默认提供的 BeanInfo 不同。 还 可以把所有 FeatureDescriptor 的"专家(expert)"模式打开,看看基 本功能和高级功能有什么区别。 总结 这一章只是想跟你介绍一下 Swing 的强大功能然后领你入门,这样当 你知道相对而言 Swing 有多简单之后,你就能自己去探路了。你看到 的这些已经能大致满足 UI 设计之需了。但是 Swing 不止于此;它的 目标是要成为一种功能齐全的 UI 设计工具。只要你能想到,它都有 办法能作到。如果你在这里找不到你想要的,那么研究一下 Sun 的 JDK 文档吧,或者去搜 Web,如果还不行,就去找一本 Swing 的专著。 TIJ 的学习暂时就告与段落了,因为 3th 目前网上翻译只到第 14 章, 第三次看这本书,算是比较明白了。但是从第 8 章开始,很多地方还 是有一些不太懂的地方。特别是 I/O 部分,需要稍后再尽快加强一下。 总之,再第三次看了才发现这本书的好,真的实在太经典了,我觉得 自己最少应该再看个 2~3 遍才差不多。最后,感谢本书的作者 Bruce

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Eckel 的无私奉献,写了这么好的一本书而且还免费放到网上。当然 还要感谢 shhgs,这位热心的网友的翻译,对于我这种英盲来说,这 种帮助实在是太大了。作者和译者的无私奉献的精神值得我们大家学 习,和那些惟利是图,见钱眼开的人来说。这两位的风格,人品何止 高出一两倍,实在是小辈的榜样和偶像,真的是万分的敬仰之情难于 言表。

PDF 文件使用 "pdfFactory" 试用版本创建 www.fineprint.cn

Related Documents

Java Basic Details
November 2019 56
Basic Java
November 2019 17
Basic Java
November 2019 22
Basic Java Chapter 3
June 2020 4
Java Basic Book 2
October 2019 24