Java编程思想读书笔记-1(第5-7章)
作者:未知 时间:2005-07-24 21:15 出处:JR 责编:My FAQ
摘要:Java编程思想读书笔记-1(第5-7章)
第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 基本成员的缺省值
1) 当class的某个成员属于基本型别时,即使你没有为它提供初值,Java仍保
证它有一个缺省值。
2) 只有当变量身份是“class内的成员时,Java才保证为该变量提供初值。
三.函数(Mehtods),引数(arguments),返回值(return values)
1. 引数列
当引数传递的是对象时,传递的是对象的reference。
四.注解用内嵌式文档
Java提供两种注解风格:/*XXXX*/、//XXXX
第3章 控制程序流程
一.使用Java运算符
1.关系运算符
1.) 当对两个对象运用关系运算符进行比较时,比较的是object reference,如:
java/lang/Integer.java.html" target="_blank">Integer n1 = new
java/lang/Integer.java.html" target="_blank">Integer(3);
java/lang/Integer.java.html" target="_blank">Integer n2 = new
java/lang/Integer.java.html" target="_blank">Integer(3);
java/lang/System.java.html" target="_blank">System.out.println(n1==n2);
结果为false,因为两个object reference(n1和n2)值是不同的
2) quals()的缺省行为也是拿referenct来比较。不过Java中的class覆写了equals方
法,如:
java/lang/Integer.java.html" target="_blank">Integer n1 = new
java/lang/Integer.java.html" target="_blank">Integer(3);
java/lang/Integer.java.html" target="_blank">Integer n2 = new
java/lang/Integer.java.html" target="_blank">Integer(3);
java/lang/System.java.html" target="_blank">System.out.println(n1.quals(n2));//值为
true
2. 逻辑运算符
1) 只能将and、or、not施用于boolean值身上。如果逻辑运算符两边的值存在
non-boolean值,将会出错,如:
int test1 = 1;
java/lang/System.java.html" target="_blank">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
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. 区分重载函数
由于只能从函数名和函数的引数列来区分两个函数,而重载函数具有相同的函
数名称,所以每个重载函数都必须具备独一无二的引数列。
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
{
public static void main(java/lang/String.java.html" target="_blank">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(java/lang/String.java.html" target="_blank">String ss){}
Flower(int petals, Sting ss){
//petals++;调用另一个构造函数的语句必须在最起始的位置
this(petals);
//this(ss);会产生错误,因为在一个构造函数中只能调用一个构造函数 }
}
**: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{
java/lang/String.java.html" target="_blank">String name = “”;
Movie(){}
public Movie(java/lang/String.java.html" target="_blank">String name) { this.name
= name; }
public static java/lang/String.java.html" target="_blank">String getName() { return
name; }
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ //下面两名先产生实例后再调用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++;
}
将发生编译错误,因为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) 调用构造函数。
例:
class Cup{
Cup(int marker){
java/lang/System.java.html" target="_blank">System.out.println("Cup(" + marker
+ ")");
}
void f(int marker){
java/lang/System.java.html" target="_blank">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);
}
Cups(){
java/lang/System.java.html" target="_blank">System.out.println("Cups()"); }
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ java/lang/System.java.html" target="_blank">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()
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实例的内
存空间。如
java/lang/String.java.html" target="_blank">String s = “hello”;
变量s存放的是一个reference,这个reference指向一个存有String实例的内存空间
。
c)对于基本型别数组,存放的是reference数组,数组中的每一个reference都指
向一个class实例的内存空间。如
int[] ia = {10, 11, 12};
数组ia存放的是一个reference数组,数组中的每一个reference都指向一个的int实
例的内存空间。
d)对于class数组,存放的是reference数组,数组中的每一个reference都指向一
个的class实例的内存空间。如
java/lang/String.java.html" target="_blank">String[] sa = {“hello1”, “hello2”, “
hello3”};
数组sa存放的是一个reference数组,数组中的每一个reference都指向一个的String
实例的内存空间。
3) 任何数组都要进行初始化,使用没有进行初始化的数组会产生运行时错误
,如:
int[] iArr;
java/lang/System.java.html" target="_blank">System.out.pritnln(iArr[0]);//产生错误
,因为iArr还未初始化
数组初始化可在任何地方,可用以下方法来对数组进行初始化:
a) int[] iArr = {1,1,1,1};//数组的长度为{}元素的个数
b) int i = 10;
int[] iArr = new int;//数组的长度可为变量(这在C/C++中不行)
java/lang/System.java.html" target="_blank">System.out.println(iArr[0]);//iArr[0]是
一个int,自动初始化值为0
java/lang/Integer.java.html" target="_blank">Integer[] iArr2 = new
java/lang/Integer.java.html" target="_blank">Integer;
java/lang/System.java.html" target="_blank">System.out.println(iArr2[0]);//iArr[0]是
一个reference,自动初始为null
I) 对于基本型别数组,new产生的是用于存放数据的数组;否则,产生的只
是存放reference的数组。
II) new可用来初始化基本型别的数组,但不能产生non-array的 基本型别
数据。
c) int[] iArr = new int[]{1,1,1,1};
java/lang/Integer.java.html" target="_blank">Integer[] iArr2 = new
java/lang/Integer.java.html" target="_blank">Integer[]{new
java/lang/Integer.java.html" target="_blank">Integer(1), new
java/lang/Integer.java.html" target="_blank">Integer(2)};
3. 多维数组(Multidimensional)arrays
多维数组每一维的大小可以不一样,如:
java/lang/Integer.java.html" target="_blank">Integer[][][] a5;
a5 = new java/lang/Integer.java.html" target="_blank">Integer[3];
for(int i=0; i<a5.length; i++)
a5 = new java/lang/Integer.java.html" target="_blank">Integer[i+1];
for(int j=0; j<a5.length
a5[j] = new java/lang/Integer.java.html" target="_blank">Integer[i+j+1];
第5章 隐藏实现细节
一.Java访问权限饰词(access specifiers)
Java有public、protect、friendly、private四种访问权限,并且这四访问权限的访
问范围越来越小。
1. friendly
1) 果一个class内的数据成员或方法没有任何权限饰词,那么它的缺省访问权
限就是friendly。同一个package内的其它所有classes都可以访问friendly成员,但
对package以外的classes则形同private。
2) 对于同一个文件夹下的、没有用package的classes,Java会自动将这些
classes初见为隶属于该目录的default package,可以相互调用class中的friendly成
员。如以下两个class分别在同一个文件夹的两个文件中,虽然没有引入package
,但隶属于相同的default package。
class Sundae{
//以下两个方法缺省为friendly
Sundae(){}
java/lang/Void.java.html" target="_blank">Void f() {java/lang/System.java.html"
target="_blank">System.out.println(“Sundae.f()”);
}
public class IceCream{
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
Sundae x = new Sundae();
x.f();
}
}
2. public:可以被任何class调用
3. private:private成员只能在成员所属的class内被调用,如:
class Sundae{
private Sundae(){}//只能在Sundae class中被调用
Sundae(int i) {}
static Sundae makASundae() {
return new Sundae();
}
}
public class IceCream{
public static void main(java/lang/String.java.html" target="_blank">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访问。
二.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;
}
public void f(java/lang/String.java.html" target="_blank">String msg){
java/lang/System.java.html" target="_blank">System.out.println("f(" + msg +
")");
}
}
public class Lunch{
public static void main(java/lang/String.java.html" target="_blank">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)
java/lang/System.java.html" target="_blank">System.out.println("Cleanser.prt()"); }
}
public class ExplicitStatic extends Cleanser{
public void prt(){
java/lang/System.java.html" target="_blank">System.out.println("ExplicitStatic.prt
()");
}
public static void main(java/lang/String.java.html" target="_blank">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(){java/lang/System.java.html" target="_blank">System.out.println("Base()");} public void scrub() { java/lang/System.java.html"
target="_blank">System.out.println(" Base.scrub()"); }
}
class Cleanser extends Base{
private java/lang/String.java.html" target="_blank">String s = new
java/lang/String.java.html" target="_blank">String("Cleanser");
public void append(java/lang/String.java.html" target="_blank">String a) { s+=a; } public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { java/lang/System.java.html"
target="_blank">System.out.println(s); }
Cleanser(){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(): " +
s);
}
public static void testStatic(){
java/lang/System.java.html" target="_blank">System.out.println("testStatic()"); }
public static void main(java/lang/String.java.html" target="_blank">String[] args){ Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub(); x.print();
}
}
public class ExplicitStatic extends Cleanser{
ExplicitStatic(){
java/lang/System.java.html" target="_blank">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(java/lang/String.java.html" target="_blank">String[] args){ ExplicitStatic x = new ExplicitStatic();
x.dilute(); x.apply(); x.scrub(); x.foam();
x.print(); java/lang/System.java.html" target="_blank">System.out.println("Test
base class:");
Cleanser.main(args);
testStatic();
}
}
运行结果:
Base()
Cleanser(): Cleanser
ExplicitStatic()
testStatic()
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 java/lang/String.java.html" target="_blank">String baseS = "Base";//(a
)
//private String baseS = "Base";
Base(){java/lang/System.java.html" target="_blank">System.out.println("Base()");} }
class Cleanser extends Base{
protected java/lang/String.java.html" target="_blank">String baseS = "Cleanser";//
(b)
public java/lang/String.java.html" target="_blank">String s = new
java/lang/String.java.html" target="_blank">String("Cleanser");
Cleanser(){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(): " +
s);
}
Cleanser(java/lang/String.java.html" target="_blank">String a){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(" + a +
"): s = " + s );
}
}
public class ExplicitStatic extends Cleanser{
java/lang/String.java.html" target="_blank">String s2 = s;
java/lang/String.java.html" target="_blank">String baseS = super.baseS; //(c) ExplicitStatic(){
super("ExplicitStatic");
java/lang/System.java.html" target="_blank">System.out.println("ExplicitStatic():s2
= " + s2 + ", baseS = "
+ baseS + "super.baseS = " + super.baseS);
baseS = "ExplicitStatic";
java/lang/System.java.html" target="_blank">System.out.println("baseS = " +
baseS + " , super.baseS = " + super.baseS);
}
public static void main(java/lang/String.java.html" target="_blank">String[] args){ ExplicitStatic x = new ExplicitStatic();
}
}
结果1:
Base()
Cleanser(ExplicitStatic): s = Cleanser
ExplicitStatic():s2 = Cleanser, baseS = Cleanser,super.baseS = Cleanser
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文件)
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)处的代码注释掉,那么初始化工作到此就结束了。
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);
Base(){
java/lang/System.java.html" target="_blank">System.out.println("Base()");
java/lang/System.java.html" target="_blank">System.out.println("s1 = " + s1 + "
,i1 = " + i1);
draw();//(d)
}
void draw(){
java/lang/System.java.html" target="_blank">System.out.println("base.draw:s1 =
" + s1 + " ,i1 = " + i1);
}
static int prt(java/lang/String.java.html" target="_blank">String s, int num) { java/lang/System.java.html" target="_blank">System.out.println(s);
return num;
}
}
class Cleanser extends Base{
static int s2 = prt("s2 initialized.", 21);
int i2 = prt("i2 initialized.", 22);
Cleanser(){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser()"); java/lang/System.java.html" target="_blank">System.out.println("s2 = " + s2 + "
,i2 = " + i2);
}
Cleanser(java/lang/String.java.html" target="_blank">String a){
//super();(b)
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(" + a +
")");
java/lang/System.java.html" target="_blank">System.out.println("s2 = " + s2 + "
,i2 = " + i2);
}
void draw(){
java/lang/System.java.html" target="_blank">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)
java/lang/System.java.html" target="_blank">System.out.println("ExplicitStatic()"); java/lang/System.java.html" target="_blank">System.out.println("s3 = " + s3 + "
,i3 = " + i3);
}
public static void main(java/lang/String.java.html" target="_blank">String[] args){ ExplicitStatic x = new ExplicitStatic();//(c)
}
}
结果:
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中无法调用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 java/lang/String.java.html" target="_blank">String baseS = "Base"; //private String baseS = "Base";
Base(){java/lang/System.java.html" target="_blank">System.out.println("Base()");} }
class Cleanser extends Base{
protected java/lang/String.java.html" target="_blank">String baseS = "Cleanser"; public java/lang/String.java.html" target="_blank">String s = new
java/lang/String.java.html" target="_blank">String("Cleanser");
Cleanser(){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(): " +
s);
}
Cleanser(java/lang/String.java.html" target="_blank">String a){
java/lang/System.java.html" target="_blank">System.out.println("Cleanser(" + a +
"): s = " + s );
}
}
public class ExplicitStatic extends Cleanser{
java/lang/String.java.html" target="_blank">String s2 = s;
java/lang/String.java.html" target="_blank">String baseS = super.baseS;
ExplicitStatic(){
super("ExplicitStatic");
java/lang/System.java.html" target="_blank">System.out.println("ExplicitStatic():s2
= " + s2 + ", baseS = "
+ baseS + "super.baseS = " + super.baseS);
baseS = "ExplicitStatic";
java/lang/System.java.html" target="_blank">System.out.println("baseS = " +
baseS + " , super.baseS = " + super.baseS);
}
public static void main(java/lang/String.java.html" target="_blank">String[] args){ ExplicitStatic x = new ExplicitStatic();
}
}
结果:
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(java/lang/String.java.html" target="_blank">String[] args){ final Value v = new Value();//v.i = 1
v.i++;//v.i = 2
//v = new Value();
}
}
由于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)
java/lang/System.java.html" target="_blank">System.out.println("i = " + i + ", ib
= " + ib);
}
public static void main(java/lang/String.java.html" target="_blank">String[] args){ ExplicitStatic ex = new ExplicitStatic();
}
}
ib为blank finals,所以可以在构造函数中进行初始化。如果把(a)处的代码
注释掉,则ib没有初值,编译出错。而i在定义处已进行了初始化,则不能改变i
的值,(b)处的代码编译错误。
**:非blank finals成员即使在构造函数中也不能更改其值
2.Final 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(java/lang/String.java.html" target="_blank">String[]
args){
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;//final class中的non-final数据成员可以被改变
n.j++;
}
}
2)final class中的所有函数也都自然是final,因为没有人能够加以覆写。
第7章 多态
一.再探向上转型(upcasting)
将某个object reference视为一个“reference to base type“的动作,称为
向上转型。
1. Upcasting后调用某个函数时,如果derived class中覆写了该函数,则会调
用derived class中的函数;否则,会调用base class中的函数。如
class First{
public void prt(){
java/lang/System.java.html" target="_blank">System.out.println("First"); }
}
class Second extends First{
//(a)
public void prt(){
java/lang/System.java.html" target="_blank">System.out.println("Second"); }
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ First n = new Second();
n.prt();;
}
}
结果为Second。如果当Second class中的prt()函数注释掉,将输出First。
2. 向上转型后只能调用base class中被derived class覆写的函数。
/*
abstract class First{
int i = 122;
public void prt(){
System.out.println("First.i = " + i);
}
public abstract void prt(First f);
}
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);;
}
}
*/
class First{
public void prt(){
java/lang/System.java.html" target="_blank">System.out.println("First"); }
}
class Second extends First{
//(a)
public void prt(){
java/lang/System.java.html" target="_blank">System.out.println("Second"); }
public void prt(int i){//(a)
java/lang/System.java.html" target="_blank">System.out.println("Second.i = " + i); }
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ First n = new Second();
n.prt(3);
}
}
(a)处的函数只是Second class中的函数,所以不能通过n.prt(3)进行调用。
二.Abstract class和Abstract methods
1. 如果一个class中存在abstract class,则class也必须被声明为abstract class
。
2. abstract class不能被实例化。
3. 如果base class是一个abstract class,那么derived class必须实现base
class中所有的abstract methods;否则,derived class也必须被声明为abstract
class。
三.其它要点
1. 纯粹继承与扩充
纯粹继承:只有base class所建议的函数,才被derived class加以覆写。 扩充:除了覆写base class的函数,还实现了自己的函数
abstract class First{
public abstract void f();
public abstract void g();
}
//纯粹继承
class Second extends First{
public void f(){}
public void g(){}
}
//扩充
class Third extends First{
public void f(){}
public void g(){}
public void u(){}//base class不存在的函数
}
2. 向下转型
1) 向下转型时只能调用base class中被覆写过的函数
2) 只有本来就为derived 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(java/lang/String.java.html" target="_blank">String[] args){ 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,而接口中未被实现的函数在这个class中为abstract class。
interface Interface{
public void f();
public void g();
}
abstract class First implements Interface{
public void f(){}
}
class Second extends First{
public void g(){}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">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
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(java/lang/String.java.html" target="_blank">String[] args){ MyInterface x = new First();
// MyInterface的数据成员I为static,可直接调用
java/lang/System.java.html" target="_blank">System.out.println("MyInterface.i = "
+ MyInterface.i + " , x.i = " + x.i);
// MyInterface的数据成员I为final,不能修改
//x.i++;
// MyInterface.i++;
}
}
4. 多重继承
1) devriced class可以同时继承多个interface和一个abstract或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(); }
static void h(Hero x){
x.fight(); x.swim();
}
public static void main(java/lang/String.java.html" target="_blank">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{
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(){} } (b)
interface I4 extends I1, I3{} //(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时,是无论如何也不可能编译通过的。
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() {}
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() {}
}
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(java/lang/String.java.html" target="_blank">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) 当实现某个接口时,无需实现其中嵌套的接口。
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 java/lang/String.java.html" target="_blank">String label;
Destination(java/lang/String.java.html" target="_blank">String whereTo){ label = whereTo;
}
java/lang/String.java.html" target="_blank">String readLabel() { return label; } }
public Destination to(java/lang/String.java.html" target="_blank">String s){ //在outer class的non-static函数中可直接产生inner class对象
return new Destination(s); //(1)
}
public Contents cont(){
return new Contents(); //(1)
}
public void ship(java/lang/String.java.html" target="_blank">String dest){
//在outer class的non-static函数中可直接通过InnerClassName
//来指定对象型别
Contents c = cont();
Destination d = to(dest);
java/lang/System.java.html" target="_blank">System.out.println(d.readLabel()); }
public static void main(java/lang/String.java.html" target="_blank">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();
}
}
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{
java/lang/String.java.html" target="_blank">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 java/lang/String.java.html" target="_blank">String label;
PDestination(java/lang/String.java.html" target="_blank">String whereTo){ label = whereTo;
}
public java/lang/String.java.html" target="_blank">String readLabel() { return
label; }
}
public Destination to(java/lang/String.java.html" target="_blank">String s){ return new PDestination(s);
}
public Contents cont(){
return new PContents();
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ Parcel3 p = new Parcel3();
//把inner class对象向上转型
Contents c = p.cont();
Destination d = p.to("Borneo");
}
}
虽然我们不能在ExplicitStatic class无法调用Pcontents class,但我们把一个
Pcontents class对象向上转型为Contents,就可对之进行调用。
4) inner class的作用域为定义该inner class的scope内。但inner 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();
}
//不能在函数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{
public static void main(java/lang/String.java.html" target="_blank">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 inner class
1) inner class可以访问enclosing class的所有成员(包括private成员),就像
inner class自己拥有这些成员一样。即inner class天生具有对enclosing class的
所有成员的访问权力。
2) Inner class对象被产生时,一定要关联到其enclosing class的某个对象(这
个enclosing class对象就是Inner class对象的制造者)。建构inner class对象的
同时,得有其enclosing class对象的reference才行。
原因:因为inner class可以访问enclosing class的所有成员,那么当产生一个
inner class时,编译器会自动为inner class对象添加一个指向enclosing class对
象的reference(这个reference是隐藏的)。所以Inner class被产生时,一定要关
联到其enclosing class的某个对象。
3) 同一个enclosing class对象产生出来的inner class对象访问的是同一个
enclosing class对象中的成员。
interface Destination{
java/lang/String.java.html" target="_blank">String readLabel();
}
interface Contents{
int value();
}
class Parcel3{
int i1 = 10;
private java/lang/String.java.html" target="_blank">String s1 = "Parcel3_"; Parcel3(java/lang/String.java.html" target="_blank">String s){
s1 += s;
}
private class PContents implements Contents{
//可调用enclosing class的成员 (1)
private int i2 = i1;
private java/lang/String.java.html" target="_blank">String s2 = s1;
PContents(int num){
java/lang/System.java.html" target="_blank">System.out.println("" + num + ":
i2 = " + i2 + ",s2 = " + s2);
}
public int value() { return 1; }
}
public Contents cont(int i){
return new PContents(i);
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">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 class的成员。结果表明,同一个
enclosing class对象p1产生的inner class对象调用的是同一个enclosing 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 java/lang/String.java.html" target="_blank">String s1 = "Parcel3_";
private java/lang/String.java.html" target="_blank">String s11 = “Parcel3_”; Parcel1(java/lang/String.java.html" target="_blank">String s){
s1 += s;
}
protected static class PContents implements Contents{
//只能访问enclosing class中的s1
java/lang/String.java.html" target="_blank">String s2 = s1;
//s11不是static成员,不能访问
//String 22 = s11;
PContents(int num){
java/lang/System.java.html" target="_blank">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(java/lang/String.java.html" target="_blank">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 class是静态的,所以在(1)处不通过enclosing class对
象而是通过静态函数来直接产生其对象。
2.3 无论inner class被嵌套置放的层次有多深,且所有outer class的成员都可 被它访问。
class MNA{
private void f() {}
class A{
private void g() {}
class B{
void h(){
g();
f();
}
}
}
}
3. 如何产生inner class对象的总结
3.1 non-static内隐类
1) 在enclosing class的non-static函数中可以直接通过new来产生
2) 在enclosing class的static函数或其它的class中,必须同时存在一个enclosing
class对象(原因在上面2.1已说明)。
interface Contents{
int value();
}
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(java/lang/String.java.html" target="_blank">String[] args){ Parcel1 p1 = new Parcel1();
//在static函数中通过外部类Parcel1对象来产生
Contents c1 = p1.cont(); //调用函数
c1 = p1.new PContents(); //通过new
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ //通过外部类Parcel1对象来产生
Parcel1 p1 = new Parcel1();
Contents c1 = p1.cont(); //调用函数
c1 = p1.new PContents(); //通过new
}
}
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(java/lang/String.java.html" target="_blank">String[] args){ Parcel1 p1 = new Parcel1();
//在static函数中通过外部类Parcel1对象来产生
Contents c1 = p1.cont(); //调用函数
c1 = p1.new PContents(); //通过new
//在static函数中直接通过new来产生PContents class对象
c1 = new PContents(); //(1)
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">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 class的继承
1) inner class可被继承。inner class的drived class的drfault构造函数必须传入
一个reference指向outer object,并在构造函数中调用outer class的构造函数。
class WithInner{
class Inner{}
}
class InheritInner extends WithInner.Inner
{
//InheritInner(){} 编译错误
InheritInner(WithInner wi) { wi.super(); }
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
2) 覆写inner class不具备多态特性。
class Egg{
class Yolk{
public Yolk(){
java/lang/System.java.html" target="_blank">System.out.println("Egg.Yolk()"); }
}
private Yolk y;
public Egg(){
java/lang/System.java.html" target="_blank">System.out.println("New Egg()"); y = new Yolk(); //(1)
}
}
class BigEgg extends Egg{
//(2)尝试覆写inner class
class Yolk{
public Yolk(){
java/lang/System.java.html" target="_blank">System.out.println("BigEgg.Yolk
()");
}
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ new BigEgg(); //(3)
}
}
结果为:
New Egg()
Egg.Yolk()
在(2)中我们尝试覆写inner class。当通过(3)产生一个BigEgg时,会调用
Egg的构造函数。在Egg的构造函数的(1)处产生的是Egg.Yolk class对象,而
不是子类BigEgg.Yolk class对象。
**:如上所示,上述两个inner class是完全独立的个体,各有其专属的命名
空间。
欢迎光临本站,本站刚开始起步 以后不断更新资源,你们的留言就是我们最大的
动力!
[楼 主] | Posted: 2006-04-18 18:55
视频资源
级别: 管理员
精华: 12
发帖: 1009
威望: 758 点
资源币: 125356 币
贡献值: 77 点
好评度: 271 点
在线时间:1257(小时)
注册时间:2005-11-12
最后登录:2007-02-12
第9章 持有你的对象
一. 容器简介
1. 容器的分类
1.1. Collection:一组各自独立的元素,即其内的每个位置仅持有一个元素。
1) List:以元素安插的次序来放置元素,不会重新排列。
2) Set:不接爱重复元素,它会使用自己内部的一个排列机制
1.2. Map:一群成对的key-value对象,即所持有的是key-value pairs。 Map中不能有重复的key,它拥有自己的内部排列机制。
2. 容器中的元素类型都为Object。从容器取得元素时,必须把它转换成原来的
类型。
二. 容器的详细介绍
1. Collection
Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator。
1.1. List
1.1.1 List(interface):List为Collectin加入了一些函数,使它可以在List内进行
安插和移除动作。List会产生ListIterator,通过它可以从两个方向来对List进行
走访,也可以在List之内进行元素的安插和移除。
1.1.2 ArrayList:可以快速随机访问;但当元素的安插或移除发生在List中央位
置时,效率很差。不宜用ArrayList来进行安插和移除操作。
1.1.3 LinkedList:与ArrayList相反,适合用来进行安插和移除,但随机访问的
速度较慢。此外,可以通过LinkedList来实现stack、queue、deque。
1) LinkedList中的addFirst()、addLast()、getFirst()、getLast()、removeFirst()、
removeLast()函数未定义于任何一个interface或base class中,所以只能用于
LinkedList中。
1.2. Set
1.2.1 Set(interface):Set具有和Collection一模一样的interface(区别:List加入
了自己的函数),所以Set就是一个Collection,只不过其行为不同罢了。加至
Set内的每个元素都必须独一无二,不与其他元素重复;Set不允许持有重复元
素,每个元素都必须定义equals()以判断所谓的独一性。
1.2.2 HashSet:一种把查找时间看得很重要的Sets。所有元素都必须定义
hashCode()。
1.2.3 TreeSet:底层结构为tree的一种有序的Set。
2. Map
2.1. Map:维护key-value的关联性,使你可以使用key来查找value。
1) KeySet()函数和values()函数
import java.util.*;
public class ExplicitStatic{
public static void printKeys(java/util/Map.java.html" target="_blank">Map m){ java/lang/System.java.html" target="_blank">System.out.print("Size = " + m.size
());
java/lang/System.java.html" target="_blank">System.out.println(" , Keys: " +
m.keySet());
}
public static void printValues(java/util/Map.java.html" target="_blank">Map m){ java/lang/System.java.html" target="_blank">System.out.println("Values: " +
m.values());
}
public static void test(java/util/Map.java.html" target="_blank">Map m){ for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
printKeys(m);
printValues(m);
java/lang/System.java.html" target="_blank">System.out.println("km1 - " + m.get
("km1"));
java/util/Set.java.html" target="_blank">Set keys = m.keySet(); //(1)
java/util/Collection.java.html" target="_blank">Collection values = m.values(); //
(2)
keys.remove("km2"); //(3)
values.remove("m1"); //(4)
java/lang/System.java.html" target="_blank">System.out.println("km1 - " + m.get
("km1"));
printKeys(m);
printValues(m);
}
public static void main(java/lang/String.java.html" target="_blank">String[] args){ java/lang/System.java.html" target="_blank">System.out.println("Testing
HashMap");
test(new java/util/HashMap.java.html" target="_blank">HashMap());
}
}
结果为:
Testing HashMap
Size = 9 , Keys: [km5, km4, km3, km2, km1, km9, km8, km7, km6]
Values: [m5, m4, m3, m2, m1, m9, m8, m7, m6]
km1 - m1 //执行(3)(4)之前
km1 - null
Size = 7 , Keys: [km5, km4, km3, km9, km8, km7, km6] //(5)
Values: [m5, m4, m3, m9, m8, m7, m6] //(6)
在(1)(2)处代码分别得到了Map中的keys和values。从执行(3)(4)前后
的代码可知,对通过keySet()和values()函数取得的值进行修改会反映到Map本身
。
(3)中删除的是值为“km2”的key,(4)删除的是值为“m1”的value,且它
们是同一个key-value pair,但结果(5)(6)表明,Map中删除的是两个key-pair
。从而可知,只要删除了Map中的key或value中的一个,那么整个key-value pair
就会被删除。
2.2. HashMap:可在常量时间内安插元素,或找出一组key-value pair。通过其
构造函数,使用者可以调整效能表现,因为它允许你设定capacity(容量)和
loadfactor(负载因子)。
2.3. TreeMap:当你检视其中的key或key-value pairs时,会以排序形式出现,
让你得到以排序形式得到结果。TreeMap是惟一具有subMap()的一个Map,这个
函数让你得以返回tree中的部分组成。
三. HashMap的工作原理
1. 如何实现一个Map
1.1 与Map相关的知识
1.1.1 Map.Entry接口
一个实现了Map.Entry接口的类代表的是一个Map中的条目(一个key-value pair
)。所以一个Map中必须要有一个实现了Map.Entry接口的类,并用这个类来存
放Map中的key-value pair。
1.1.2 public abstract Set entrySet()函数
1) entrySet()函数返回一个Set,并且Set中的每一个元素都是一个Map.Entry类型
的对象。在entrySet()函数中要把Map中所有的key-value pair以Map.Entry封装后
存入Set中的。
2) 当对Map进行修改操作后,entrySet()函数都会被调用。所以对Map的修改也
会产生对这个Set的修改。
3) 当用这个Set的iterator进行操作时,不能进行add和addAll的操作。
1.2 实现一个简单的Map的实例
import java.util.*;
/**
* MPair类实现了Map.Entry
*/
class MPair
implements java/util/Map.java.html" target="_blank">Map.Entry,
java/lang/Comparable.java.html" target="_blank">Comparable{
java/lang/Object.java.html" target="_blank">Object key, value; //key和value分别
用来存放Map中的key和value
MPair(java/lang/Object.java.html" target="_blank">Object k,
java/lang/Object.java.html" target="_blank">Object v){
key = k;
value = v;
}
//下面方法实现了Map.Entry接口中的方法
public java/lang/Object.java.html" target="_blank">Object getKey() { return key; } public java/lang/Object.java.html" target="_blank">Object getValue() { return
value; }
public java/lang/Object.java.html" target="_blank">Object setValue
(java/lang/Object.java.html" target="_blank">Object v){
java/lang/Object.java.html" target="_blank">Object result = value;
value = v;
return result;
}
//下面方法实现了Comparable接口中的方法
public boolean equals(java/lang/Object.java.html" target="_blank">Object o){ return key.equals(((MPair)o).key);
}
public int compareTo(java/lang/Object.java.html" target="_blank">Object rv){ return ((java/lang/Comparable.java.html" target="_blank">Comparable)
key).compareTo(((MPair)rv).key);
}
}
class SlowMap extends java/util/AbstractMap.java.html"
target="_blank">AbstractMap{
private java/util/ArrayList.java.html" target="_blank">ArrayList
keys = new java/util/ArrayList.java.html" target="_blank">ArrayList(), values = new java/util/ArrayList.java.html" target="_blank">ArrayList(); public java/lang/Object.java.html" target="_blank">Object put
(java/lang/Object.java.html" target="_blank">Object key,
java/lang/Object.java.html" target="_blank">Object value){
java/lang/Object.java.html" target="_blank">Object result = get(key); if(!keys.contains(key)){ //(1)
keys.add(key);
values.add(value);
}
else
values.set(keys.indexOf(key), value);
return result;
}
public java/lang/Object.java.html" target="_blank">Object get
(java/lang/Object.java.html" target="_blank">Object key){
if(!keys.contains(key)){
return null;
}
else
return values.get(keys.indexOf(key));
}
//用Mpair封装Map中的key-value pair并存入Set中
public java/util/Set.java.html" target="_blank">Set entrySet(){
java/util/Set.java.html" target="_blank">Set entries = new
java/util/HashSet.java.html" target="_blank">HashSet();
java/util/Iterator.java.html" target="_blank">Iterator
ki = keys.iterator(),
vi = values.iterator();
while(ki.hasNext())
entries.add(new MPair(ki.next(), vi.next()));
return entries;
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ SlowMap m = new SlowMap();
for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
java/lang/System.java.html" target="_blank">System.out.println(m); }
}
在上面代码的(1)处,我们要从ArrayList中查找出是否具有key值,而这个查
找过程线性查找,且key不具任何顺序,所以速度会很慢。
2. 与HashMap相关的几个函数
1) hashCode()函数
Object.hashCode()函数会为对象产生hash code。如果一个类没有实现hashCode()
函数,那么在缺省情况下将返回它的对象的内存地址。
2) equals()函数
Object.equals()在比较两个对象时,比较的是两个对象的内存地址。
3. HashMap的工作原理
3.1 用array来表现key的信息。每个key的hashCode()函数会为key产生一个hash
code,而key的hash code作为array的索引。如假设有一个名为bucket的arrsy,
姥一个hash code为2的key就被索引到bucket[2],key所对应的值也在bucket[2]中
。
3.1 由于array中存放的是value值,而HashMap的元素个数可以是无限的,所以
array中的元素指向的不是某个key的value,而是指向具有相同的hash code的key
的value值(也就是说指向的是一串values值)。如假设array被定义为LinkedList
[]bucket = new LinkedList[10],那么bucket[2]中存放的是所有hash code值为2的
key的value。
欢迎光临本站,本站刚开始起步 以后不断更新资源,你们的留言就是我们最大的
动力!
[1 楼] | Posted: 2006-04-18 18:55
视频资源
级别: 管理员
精华: 12
发帖: 1009
威望: 758 点
资源币: 125356 币
贡献值: 77 点
好评度: 271 点
在线时间:1257(小时)
注册时间:2005-11-12
最后登录:2007-02-12
4. 自己实现一个简单的HashMap及其原理
4.1 在put()方法中:
1) 首先通过key得出要插入的key-value pair的hash code,并这个hash code
作为索引在数组bucket中找出key所对应的元素。
2) 把要插入的key-value pair封装成实现了Map.Entry接口的类的一个对象。
3) 在操作1)所找出的数组元素(也是一个LinkedList)中查看是否有与要插入
的key-value pair的key相同的元素,如果有,则对之进行更新;如果无,则把
要插入的key-value pair数组元素中。
4.2 在get()方法中
1) 首先通过key得出要查找的key-value pair的hash code,并这个hash code
作为索引在数组bucket中找出key所对应的元素。
2) 把要查找的key-value pair的key封装成实现了Map.Entry接口的类的一个对象
。
3) 在操作1)所找出的数组元素(也是一个LinkedList)中查看是否有与要插入
的key-value pair的key相同的元素,如果有,则返回key所对应的value;如果无
,则返回一个null。
4.3 一个实例
import java.util.*;
/**
* MPair类实现了Map.Entry
*/
class MPair
implements java/util/Map.java.html" target="_blank">Map.Entry,
java/lang/Comparable.java.html" target="_blank">Comparable{
java/lang/Object.java.html" target="_blank">Object key, value;
MPair(java/lang/Object.java.html" target="_blank">Object k,
java/lang/Object.java.html" target="_blank">Object v){
key = k;
value = v;
}
public java/lang/Object.java.html" target="_blank">Object getKey() { return key; } public java/lang/Object.java.html" target="_blank">Object getValue() { return
value; }
public java/lang/Object.java.html" target="_blank">Object setValue
(java/lang/Object.java.html" target="_blank">Object v){
java/lang/Object.java.html" target="_blank">Object result = value;
value = v;
return result;
}
/**
* 当比较两个MPair对象时,比较的是它们的key值
*/
public boolean equals(java/lang/Object.java.html" target="_blank">Object o){ return key.equals(((MPair)o).key);
}
public int compareTo(java/lang/Object.java.html" target="_blank">Object rv){ return (((java/lang/Comparable.java.html" target="_blank">Comparable)
key).compareTo(((MPair)rv).key));
}
}
class SimpleHashMap extends java/util/AbstractMap.java.html"
target="_blank">AbstractMap{
private final static int SZ = 997;
private java/util/LinkedList.java.html" target="_blank">LinkedList[] bucket = new
java/util/LinkedList.java.html" target="_blank">LinkedList[SZ];
/**
* 把key和value封装成Map.Entry的实现类后插入到array中
*/
public java/lang/Object.java.html" target="_blank">Object put
(java/lang/Object.java.html" target="_blank">Object key,
java/lang/Object.java.html" target="_blank">Object value){
java/lang/Object.java.html" target="_blank">Object result = null;
//通过key得到要插入的key-value pair的hash code
int index = key.hashCode() % SZ;
if(index < 0) index = - index;
if(bucket[index] == null)
bucket[index] = new java/util/LinkedList.java.html"
target="_blank">LinkedList();
//通过hash code找出要插入的key所对应的array中的元素
java/util/LinkedList.java.html" target="_blank">LinkedList pairs = bucket[index]; //把要插入的key-value pair封装成MPair
MPair pair = new MPair(key, value);
java/util/ListIterator.java.html" target="_blank">ListIterator it = pairs.listIterator(); boolean found = false;
//检查是否有与要插入的key相同的key存在,如果有,就对之进行更新 while(it.hasNext()){
java/lang/Object.java.html" target="_blank">Object iPair = it.next(); if(iPair.equals(iPair)){
result = ((MPair)iPair).getValue();
it.set(pair);
found = true;
break;
}
}
//如果无,则把新的key-value pair插入
if(!found)
bucket[index].add(pair);
return result;
}
public java/lang/Object.java.html" target="_blank">Object get
(java/lang/Object.java.html" target="_blank">Object key){
int index = key.hashCode() % SZ;
if(index < 0) index = -index;
if(bucket[index] == null) return null;
java/util/LinkedList.java.html" target="_blank">LinkedList pairs = bucket[index]; java/util/ListIterator.java.html" target="_blank">ListIterator it = pairs.listIterator(); MPair match = new MPair(key, null);
while(it.hasNext()){
java/lang/Object.java.html" target="_blank">Object iPair = it.next(); if(iPair.equals(match))
return ((MPair)iPair).getValue();
}
return null;
}
public java/util/Set.java.html" target="_blank">Set entrySet(){
java/util/Set.java.html" target="_blank">Set entries = new
java/util/HashSet.java.html" target="_blank">HashSet();
for(int i=0; i<bucket.length; i++){
if(bucket == null) continue;
java/util/Iterator.java.html" target="_blank">Iterator it = bucket.iterator(); while(it.hasNext())
entries.add(it.next());
}
return entries;
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ SimpleHashMap m = new SimpleHashMap();
for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
java/lang/System.java.html" target="_blank">System.out.println(m);
}
}
四. HashMap的一些其它讨论
1. 关于HashMap中的key值的使用
1.1. 以Java的库函数做为HashMap的key值时,可以直接使用。
import java.util.*;
class Counter{
int i = 1;
public java/lang/String.java.html" target="_blank">String toString(){
return java/lang/Integer.java.html" target="_blank">Integer.toString(i); }
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ java/util/HashMap.java.html" target="_blank">HashMap hm = new
java/util/HashMap.java.html" target="_blank">HashMap();
for(int i = 0; i < 10000; i++)
{
//HashMap的key的类型为Integer
java/lang/Integer.java.html" target="_blank">Integer r = new
java/lang/Integer.java.html" target="_blank">Integer((int)
(java/lang/Math.java.html" target="_blank">Math.random() * 20));
if(hm.containsKey(r))
((Counter)hm.get(r)).i++;
else
hm.put(r, new Counter());
}
java/lang/System.java.html" target="_blank">System.out.println(hm); }
}
1.2. 如果在HashMap中使用你自己撰写的classes做为key,你一定得同时覆写
hashCode()和equals()。
下面代码用自己实现的class做为key,但没有覆写hashCode()和equals()。
import java.util.*;
class Groundhog{
int ghNumber;
Groundhog(int n) { ghNumber = n; }
public java/lang/String.java.html" target="_blank">String toString(){ return "Groundhog@" + ghNumber;
}
}
class Prediction{
boolean shadow = java/lang/Math.java.html" target="_blank">Math.random()
> 0.5;
public java/lang/String.java.html" target="_blank">String toString(){ if(shadow)
return "Six more weeks of Winter!\n";
else
return "Early Spring!\n";
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
java/util/HashMap.java.html" target="_blank">HashMap hm = new
java/util/HashMap.java.html" target="_blank">HashMap();
for(int i = 1; i < 10; i++)
hm.put(new Groundhog(i), new Prediction());
java/lang/System.java.html" target="_blank">System.out.println("hm = " +
hm);
java/lang/System.java.html" target="_blank">System.out.println("Looking up
prediction for Groundhog #3:");
Groundhog gh = new Groundhog(3);
if(hm.containsKey(gh)) //(1)
java/lang/System.java.html" target="_blank">System.out.println((Prediction)
hm.get(gh));
else
java/lang/System.java.html" target="_blank">System.out.println("Key not
found: " + gh);
}
}
运行结果:
hm = {Groundhog@9=Early Spring!
, Groundhog@8=Six more weeks of Winter!
, Groundhog@7=Six more weeks of Winter!
, Groundhog@6=Early Spring!
, Groundhog@5=Early Spring!
, Groundhog@4=Early Spring!
, Groundhog@3=Early Spring!
, Groundhog@2=Early Spring!
, Groundhog@1=Six more weeks of Winter!
}
Looking up prediction for Groundhog #3:
Key not found: Groundhog@3
key没覆写hashCode()和equals(),那么在通过key取得hash code时,就会取得key
的内存地址;同样,当通过equals()函数比较两个key是否相等时,比较的也是
两个key的地址。所以(1)处代码比较的结果为false(因为两个对象的内存地
址肯定是不相同的)。显然,这不是我们要得到的结果。
为了要得到正确的结果,我们只需在作为key的类中实现hashCode()和equals()。
java.util.*;
class Groundhog2{
int ghNumber;
Groundhog2(int n) { ghNumber = n; }
public java/lang/String.java.html" target="_blank">String toString(){ return "Groundhog2@" + ghNumber;
}
/**
* 以ghNumber作为hash code
*/
public int hashCode() { return ghNumber; }
/**
*比较的是两个key的ghNumber值
*/
public boolean equals(java/lang/Object.java.html" target="_blank">Object o) {
return (o instanceof Groundhog2)
&& (ghNumber == ((Groundhog2)o).ghNumber);
}
}
class Prediction{
boolean shadow = java/lang/Math.java.html" target="_blank">Math.random()
> 0.5;
public java/lang/String.java.html" target="_blank">String toString(){ if(shadow)
return "Six more weeks of Winter!\n";
else
return "Early Spring!\n";
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
HashMap hm = new HashMap();
for(int i = 1; i < 10; i++)
hm.put(new Groundhog2(i), new Prediction());
java/lang/System.java.html" target="_blank">System.out.println("size = " +
hm.size() + " , hm = " + hm);
java/lang/System.java.html" target="_blank">System.out.println("Looking up
prediction for Groundhog #3:");
Groundhog2 gh = new Groundhog2(2);
if(hm.containsKey(gh))
java/lang/System.java.html" target="_blank">System.out.println((Prediction)
hm.get(gh));
else
java/lang/System.java.html" target="_blank">System.out.println("Key not
found: " + gh);
}
}
运行结果为:
hm = {Groundhog2@9=Early Spring!
, Groundhog2@8=Six more weeks of Winter!
, Groundhog2@7=Six more weeks of Winter!
, Groundhog2@6=Six more weeks of Winter!
, Groundhog2@5=Early Spring!
, Groundhog2@4=Early Spring!
, Groundhog2@3=Six more weeks of Winter!
, Groundhog2@2=Early Spring!
, Groundhog2@1=Early Spring!
}
Looking up prediction for Groundhog #3:
Early Spring!
在新的代码中,我们在作为key的类中实现了hashCode()和equals()函数,得到了
想要的结果。
2. HashMap的效能因子
Capacity:容量,表格中的buckets数量
Initial capacity:初始容量,表格建立之初的buckets数量。
HashMap和HashSet:各有构造函数,允许指定初始容量。
Size:大小,表格内目前所有的条目。
Load factor:负载因子,size / capacity(大小/容量)。负载因子为0,表示一个
空表格,0.5是一个半满表格,依此类推。一个轻负载表格出现碰撞(collisions
)的机会比较低,比较适合安插和查找(但会降低“通过迭代器巡访”的速度
)。在HashMap和HashSet各有构造函数中指定了负载因子后,当容器达到这个
负载因子,容器的容量(buckets个数)就会自动扩充,并将原有的对象重新导
入到新的buckets内(这称为rechashing)。HashMap缺省的负载因子值是0.75。
欢迎光临本站,本站刚开始起步 以后不断更新资源,你们的留言就是我们最大的
动力!
[2 楼] | Posted: 2006-04-18 18:56
视频资源
级别: 管理员
精华: 12
发帖: 1009
威望: 758 点
资源币: 125356 币
贡献值: 77 点
好评度: 271 点
在线时间:1257(小时)
注册时间:2005-11-12
最后登录:2007-02-12
第10章 通过异常处理错误
一. 基本异常
1. 抛出异常的原理
1) 像产生一个Java对象那样在heap上以new产生一个异常对象。
2) 停止目前的执行路线,将上述那个异常对象的reference自目前的context丢出
。
3) 异常处理机制接手工作,寻找得以继续执行的适当地点。
2. 产生一个异常对象
异常类有两个构造函数:一个default构造函数;一个带String型参数的构造函数
,参数的信息可以通过异常类中的各种方法取出。
3. 异常类的结构
1) Error是一些编译期错误或系统错误,一般无需在程序中捕捉到Error异常。
2) Exception是我们能捕捉到的异常,其中Exception异常又分为
RuntimeException和non-RuntimeException两大类异常。
二. 异常的捕捉和处理
1. 异常的捕捉
1.1 通过try?catch就可捕捉异常
import java.lang.java/lang/RuntimeException.java.html"
target="_blank">RuntimeException;
import java.lang.java/lang/NullPointerException.java.html"
target="_blank">NullPointerException;
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; import java.io.java/io/IOException.java.html" target="_blank">IOException; class TestException{
public void testSQLException() throws java/sql/SQLException.java.html"
target="_blank">SQLException {
throw new java/sql/SQLException.java.html" target="_blank">SQLException(); }
public void testIOException() throws java/io/IOException.java.html"
target="_blank">IOException {}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException te = new TestException();
try{
te.testSQLException();
te.testIOException();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch
SQLException in main");
}
catch(java/io/IOException.java.html" target="_blank">IOException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch
IOException in main");
}
catch(java/lang/Exception.java.html" target="_blank">Exception ex){ //(1) java/lang/System.java.html" target="_blank">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.java/lang/RuntimeException.java.html"
target="_blank">RuntimeException;
import java.lang.java/lang/NullPointerException.java.html"
target="_blank">NullPointerException;
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; class TestException{
//(1)异常规格中声明将抛出RuntimeException异常
public void testRuntime() throws java/lang/RuntimeException.java.html"
target="_blank">RuntimeException {}
//(2)异常规格中声明将抛出NullPointerException异常
public void testNullPointer() throws java/lang/NullPointerException.java.html"
target="_blank">NullPointerException {}
//(3)异常规格中声明将抛出non-RuntimeException异常
public void testNonRuntime() throws java/sql/SQLException.java.html"
target="_blank">SQLException {}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException te = new TestException();
te.testRuntime(); //(4)
te.testNullPointer(); //(5)
//te.testNonRuntime(); (6)
try{
te.testNonRuntime();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){} }
}
在上述代码中,(1)处在异常规格中声明将抛出RuntimeException;(2)在
异常规格中声明将抛出NullPointerException,而NullPointerException是
RuntimeException的子类,所以在调用这两个函数时,可不捕捉异常,如(4)
(5)处的代码一样直接调用。但(3)处在异常规格中声明将抛出
SQLException,而SQLException不是RuntimeException的子类,所以必须捕捉
SQLException异常。
2) 如果要在一个函数中抛出non-RuntimeException异常,则必须要在异常规格
中声明该异常。
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; import java.io.java/io/IOException.java.html" target="_blank">IOException;
class Test1{
public void f() throws java/sql/SQLException.java.html"
target="_blank">SQLException{ //(2)
throw new java/io/IOException.java.html" target="_blank">IOException
("IOException"); //(1)
}
}
public class ExplicitStatic{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ Test1 te = new Test1();
try{
te.f();
}
catch(java/lang/Exception.java.html" target="_blank">Exception ex){
java/lang/System.java.html" target="_blank">System.out.println("catch
Exception in main");
}
}
}
在(1)处抛出了一个没有在异常规格中被声明的non-RuntimeException异常,
在编译时会出错。
3. 取得异常中的信息的几个函数
1) String getMessage()、getLocalizedMessage 、toString
取得异常对象中的信息
import java.lang.java/lang/RuntimeException.java.html"
target="_blank">RuntimeException;
import java.lang.java/lang/NullPointerException.java.html"
target="_blank">NullPointerException;
Import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; import java.io.java/io/IOException.java.html" target="_blank">IOException;
class TestException{
public void tSql() throws SQLException {
java/lang/System.java.html" target="_blank">System.out.println("Originating
the exception in tSql()");
throw new SQLException("throw in tSql");
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
TestException te = new TestException();
try{
te.tSql();
}
catch(SQLException ex){
java/lang/System.java.html" target="_blank">System.out.println("catch
SQLException in main");
java/lang/System.java.html" target="_blank">System.out.println
("ex.getMessage():" + ex.getMessage());
java/lang/System.java.html" target="_blank">System.out.println
("ex.getLocalizedMessage():" +
ex.getLocalizedMessage());
java/lang/System.java.html" target="_blank">System.out.println("ex.toString
():" + ex.toString());
}
catch(java/lang/Exception.java.html" target="_blank">Exception ex){ java/lang/System.java.html" target="_blank">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()
printStackTrace打印出Throwable和其call stack trace。
FillStackTrace则在调用点设立新的stack trace信息
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; class TestException{
public static void tSql() throws java/sql/SQLException.java.html"
target="_blank">SQLException {
java/lang/System.java.html" target="_blank">System.out.println("Originating the
exception in tSql()");
throw new java/sql/SQLException.java.html" target="_blank">SQLException
("throw in tSql");
}
public void f() throws java/sql/SQLException.java.html"
target="_blank">SQLException{
try{
tSql();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("In f(),
e.printStackTrace()");
ex.printStackTrace();
throw ex; //(1)
//throw (SQLException)ex.fillInStackTrace(); (2)
}
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException te = new TestException();
try{
te.f();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){
java/lang/System.java.html" target="_blank">System.out.println("catch in main,
e.printStackTrace()");
ex.printStackTrace();
}
catch(java/lang/Exception.java.html" target="_blank">Exception ex){
java/lang/System.java.html" target="_blank">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
void TestException.f()
Test.java:9
void Test.main(java.lang.String[])
Test.java:22
java.sql.SQLException: throw in tSql
void TestException.tSql()
Test.java:5
void TestException.f()
Test.java:9
void Test.main(java.lang.String[])
Test.java:22
如果把(1)处代码注释掉,并去年(2)处代码的注释,结果将变成: Originating the exception in tSql()
In f(), e.printStackTrace()
catch in main, e.printStackTrace()
java.sql.SQLException: throw in tSql
void TestException.tSql() //(3)
Test.java:6
void TestException.f()
Test.java:10
void Test.main(java.lang.String[])
Test.java:24
java.sql.SQLException: throw in tSql
void TestException.f() //(4)
Test.java:16
void Test.main(java.lang.String[])
Test.java:24
由于在代码(2)处设立新的stack trace信息,所以异常会被认为是在f()中发
出的,所以在main()中得到的异常原始抛出点为f()(见(3)),而在f()中为
tSql()(见(6))。
3) 如果重新抛出一个不同类型的异常,也能产生fillStackTrace()函数的效果。
如果把上面代码的f()函数修改成下面的样子:
public void f() throws SQLException,IOException{
try{
tSql();
}
catch(SQLException ex){
java/lang/System.java.html" target="_blank">System.out.println("In f(),
e.printStackTrace()");
ex.printStackTrace();
throw new IOException(); //(1)
}
}
则结果为:
Originating the exception in tSql()
In f(), e.printStackTrace()
catch Exception in main
java.sql.SQLException: throw in tSql
void TestException.tSql()
Test.java:6
void TestException.f()
Test.java:10
void Test.main(java.lang.String[])
Test.java:25
java.io.IOException
void TestException.f()
Test.java:17
void Test.main(java.lang.String[])
Test.java:25
由于在(1)处抛出了一个新的类型的异常,那么在main()中捕捉到的是新的异
常,所以在main()中捕捉到的异常的原始抛出点为f()。
4. RuntimeException异常
RuntimeException及其子类所代表的异常我们在程序中不用进行捕捉,如果发
生此类异常,Java会自动抛出相应的异常对象,如:
class TestException{
public static void g(int x) {
java/lang/System.java.html" target="_blank">System.out.println("10/" + x + " = "
+ 10/x);
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException.g(2);
TestException.g(0); //(1)
}
}
上面代码在编译时不会发生错误,只有在运行时(1)处会发生错误。虽然除
法可能会存在错误,但我们不用进行捕捉,当发生错误时,Java会自动抛出相
应异常。
三. 以finally进行清理
1. 如果某段代码不管是否发生异常都要执行,那可把它改入finally块中。
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; class TestException{
public static void tSql() throws java/sql/SQLException.java.html"
target="_blank">SQLException {
java/lang/System.java.html" target="_blank">System.out.println("Originating the
exception in tSql()");
throw new java/sql/SQLException.java.html" target="_blank">SQLException
("throw in tSql");
}
public void f() throws java/sql/SQLException.java.html"
target="_blank">SQLException{
try{
tSql();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch
SQLException in f()");
throw ex; //(1)
}
finally{
java/lang/System.java.html" target="_blank">System.out.println("finally in f()"); }
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException te = new TestException();
try{
te.f();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch te.f()
SQLException in main");
}
catch(java/lang/Exception.java.html" target="_blank">Exception ex){
java/lang/System.java.html" target="_blank">System.out.println("catch te.f()
Exception in main");
}
}
}
运行结果为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
catch te.f() SQLException in main
虽然在代码(1)处重新抛出异常,但finally块中的代码仍然会被执行。
2. finally造成的异常遗失
如果在finally中执行的代码又产生异常,那么在上一层调用中所捕捉到的异常
的起始抛出点会是finally所在的函数。
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; class TestException{
public static void tSql1() throws java/sql/SQLException.java.html"
target="_blank">SQLException {
java/lang/System.java.html" target="_blank">System.out.println("Originating the
exception in tSql()");
throw new java/sql/SQLException.java.html" target="_blank">SQLException
("throw in tSql1");
}
public static void tSql2() throws java/sql/SQLException.java.html"
target="_blank">SQLException {
java/lang/System.java.html" target="_blank">System.out.println("Originating the
exception in tSql()");
throw new java/sql/SQLException.java.html" target="_blank">SQLException
("throw in tSql2");
}
public void f() throws java/sql/SQLException.java.html"
target="_blank">SQLException{
try{
tSql1();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch
SQLException in f()");
throw ex; //(2)
}
finally{
java/lang/System.java.html" target="_blank">System.out.println("finally in f()"); //tSql2(); (1)
}
}
}
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ TestException te = new TestException();
try{
te.f();
}
catch(java/sql/SQLException.java.html" target="_blank">SQLException ex){ java/lang/System.java.html" target="_blank">System.out.println("catch te.f()
SQLException in main");
java/lang/System.java.html" target="_blank">System.out.println("getMessage:
" + ex.getMessage());
java/lang/System.java.html" target="_blank">System.out.println
("printStackTrace:");
ex.printStackTrace();
}
}
}
运行结果为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
catch te.f() SQLException in main
getMessage:throw in tSql1
printStackTrace:
java.sql.SQLException: throw in tSql1
void TestException.tSql1()
Test.java:5
void TestException.f()
Test.java:13
void Test.main(java.lang.String[])
Test.java:29
从结果可以看出,在main()中能正确打印出所捕捉到的异常的起始抛出点。但
如果去掉代码(1)的注释,结果将变为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
Originating the exception in tSql()
catch te.f() SQLException in main
getMessage:throw in tSql2
printStackTrace:
java.sql.SQLException: throw in tSql2
void TestException.tSql2()
Test.java:9
void TestException.f()
Test.java:21
void Test.main(java.lang.String[])
Test.java:29
从结果可以看出,在main()中捕捉到的异常是finally中产生的异常,代码(2)
中抛出的异常丢失了。
四. 继承中异常
1. 关于构造函数中的异常
1.1 构造函数中的异常规则
某个derived class构造函数的“异常规格接口“可以比其所调用的父类的构造
函数的异常规格接口宽,但决不能变窄。
1) derived class的构造函数必须在自己的异常规格中声明所有base class构造
函数的异常规格中所声明的异常。
2) 在derived class的构造函数的异常规格中还可以声明新的异常,即声明在
base class构造函数的异常规格中没有声明的异常。
1.2 原因
当在产生一个derived class的对象时,会在derived class的构造函数中调用base
class的构造函数(初始化过程请见第6章),所以在derived class的构造函数
中可能会抛出base class构造函数的异常规格中声明的异常,因此要在derived
class的异常规格中声明base class构造函数的异常规格中声明的异常。 **:如果调用的函数的异常规格中声明了异常,那么在调用该函数的时候要
捕捉它的异常规格中声明的异常。但在derived class构造函数中却无法捕捉其
base class构造函数所掷出的异常。
2. 关于非构造函数的异常规则
2.1 某个函数的“异常规格接口“在继承和重载中可以变窄,但决不能变宽 要覆写base class的函数时,如果被覆写函数(base class中的函数)的异常规
格中声明了异常,那么覆写函数(derived class中覆写了base class中的函数的
那个函数)的异常规格中可以声明(1)与被覆写函数完全相同的异常;(2)
被覆写函数异常规格中的部分异常或其子类异常;(3)不声明异常规格。
2.2 原因
这么做是为了满足“能处理被覆写函数的代码,不用做任何修改就能处理覆写
函数的代码”的原则。
如果覆写函数的异常规格中声明了在被覆写函数的异常规格中不存在的异常,
那么能处理被覆写函数的代码就不能处理覆写函数,因为没有捕捉覆写函数中
不存在于被覆写函数中的异常声明。
import java.sql.java/sql/SQLException.java.html" target="_blank">SQLException; class BaseClass{
public void f(){}
}
class DerivedClass1 extends BaseClass{
//public void f() throws SQLException {} (1)
public void f() {} //(2)
}
public class Test{
public static void f(BaseClass bc) { bc.f(); }
/* (3)
public static void f(BaseClass bc) {
try{
bc.f();
}
catch(SQLException ex){}
}
*/
public static void main(java/lang/String.java.html" target="_blank">String[]
args){
BaseClass bc = new BaseClass();
f(bc);
DerivedClass1 dc = new DerivedClass1();
f(bc);
}
}
如果允许“异常接口“变宽,我们看看上面代码会出现什么结果。首先,我们
可以将代码(1)的注释去掉,并注释掉代码(2)。由于BaseClass class中的
被覆写f()函数没有声明异常规格,而代码(1)中覆写f()函数声明了,那么Test
class中的f(BaseClass bc)虽然能处理被覆写f()函数的调用,但不能处理覆写f()
函数的调用,因为代码覆写f()函数声明了异常规格,而f(BaseClass bc)没有进行
捕捉。那么为了处理覆写f()函数,我们还要编写代码(3)那样的处理函数。
2.3 产生对象的异常规则
在产生一个对象时,捕捉的是产生对象时所调用的构造函数中所声明的异常。
2.4 函数调用时的异常规则
1) 当把一个对象向上转型为它的base class时,并通过转型后的reference进行
函数调用时,我们要捕捉的是其base class的异常声明。
2) 当用对象的原始类型来调用函数时,只需捕捉所调用的覆写函数的异常
2.5 继承中的异常规则的一个实例
import java.lang.java/lang/Exception.java.html" target="_blank">Exception; class BaseException extends java/lang/Exception.java.html"
target="_blank">Exception {}
class Derived1Exception extends BaseException {}
class Derived2Exception extends BaseException {}
class Derived11Exception extends Derived1Exception {}
class BaseClass{
BaseClass() throws Derived1Exception {}
BaseClass(int i) throws BaseException {}
BaseClass(int i, int j) {}
//在覆写f()时不能声明异常规格
public void f() {}
//在覆写g()时可以不声明异常或声明BaseException异常或声明
//BaseException异常的子类
public void g() throws BaseException {}
//在覆写u()时可以不声明异常
public void u() throws Derived1Exception, Derived2Exception {}
}
class DerivedClass1 extends BaseClass{
//base class构造函数中声明了异常的处理方法
//声明与base class构造函数中的异常完全相同的异常
DerivedClass1(int i) throws Derived1Exception {}
//声明base class构造函数中的异常的父类异常
DerivedClass1() throws BaseException {}
//声明base class构造函数中的异常和新的异常
DerivedClass1(java/lang/String.java.html" target="_blank">String s) throws
Derived1Exception, Derived2Exception{}
//声明base class构造函数中的异常父类异常和新的异常
DerivedClass1(java/lang/String.java.html" target="_blank">String s, int i) throws
BaseException, Derived2Exception{}
DerivedClass1(int i, int j) { super(i, j); }
//注意下面这两句
DerivedClass1(int i, java/lang/String.java.html" target="_blank">String s) throws
BaseException { super(i); }
//!DerivedClass1(int i, String s) throws Derived1Exception { super(i);} public void f() {}
//下面覆写g()的几种方式
//不声明
//public void g() {}
//public void g() throws BaseException {} 声明完全相同
//声明子类异常
public void g() throws Derived1Exception {}
//声明子类异常
//public void g() throws Derived1Exception, Derived11Exception {} //下面覆写u()的几种方式
//public void u() {} 不声明
//public void u() throws Derived11Exception {} 声明部分异常的异常 //public void u() throws Derived1Exception {} 声明部分异常
//声明完全相同
//public void u() throws Derived1Exception, Derived2Exception {}
//声明子类异常
//public void u() throws Derived1Exception, Derived11Exception {} }
public class Test{
public static void main(java/lang/String.java.html" target="_blank">String[] args){ //捕捉的是相应的构造函数的异常
try{
BaseClass bc1 = new DerivedClass1();
}
catch(BaseException be) {}
try{
BaseClass bc2 = new DerivedClass1("bc2");
}
catch(Derived1Exception be1) {}
catch(Derived2Exception be2) {}
//通过向上转型来调用函数
BaseClass bc3 = new DerivedClass1(1, 1);
/*捕捉的是父类中被覆写的函数的异常,而这里捕捉的是子类中 * 被覆写的函数的异常,所以编译错误
try{
bc3.g();
}
catch(Derived1Exception be) {}
*/
//捕捉了被覆写函数的异常,是正确的
try{
bc3.g();
}
catch(BaseException be) {}
//用对象的原始类型来调用函数
DerivedClass1 dc1 = new DerivedClass1(1, 1);
//只需捕捉所调用的覆写函数的异常
try{
dc1.g();
}
catch(Derived1Exception be) {}
}
}
欢迎光临本站,本站刚开始起步 以后不断更新资源,你们的留言就是我们最大的
动力!