源码网资料下载:
第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,如: 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方法,如:
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
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(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);会产生错误,因为在一个构造函数中只能调用一个构造函数
}
}
**: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){
//下面两名先产生实例后再调用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){
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);
}
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()
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};
数组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[i];//数组的长度可为变量(这在C/C++中不行)
System.out.println(iArr[0]);//iArr[0]是一个int,自动初始化值为0
Integer[] iArr2 = new Integer[i];
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};
Integer[] iArr2 = new Integer[]{new Integer(1), new Integer(2)};
3. 多维数组(Multidimensional)arrays
多维数组每一维的大小可以不一样,如:
Integer[][][] a5;
下载源码就到源码网,
源码网资料下载:
a5 = new Integer[3];
for(int i=0; i
a5[i] = new Integer[i+1];
for(int j=0; j<A5[I].LENGTH
a5[i][j] = new 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(){}
下载源码就到源码网,
源码网资料下载:
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内被调用,如: 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访问。
二.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(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)
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()");}
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(){
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()
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 );
}
}
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
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(){
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)
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)
}
}
结果:
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 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);
}
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();
}
}
结果:
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();
}
}
由于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)处的代码注释掉,则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(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(){
System.out.println("First");
下载源码就到源码网,
源码网资料下载:
}
}
class Second extends First{
//(a)
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 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(){
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);
下载源码就到源码网,
源码网资料下载:
}
}
(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(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(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(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或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(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(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 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)
}
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();
}
}
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){
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对象向上转型为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(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{
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; }
}
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 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 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;
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 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中,必须同时存在一个enclosinglass对象(原因在上面2.1已说明)。
interface Contents{
int value();
}
class Parcel1{
protected class PContents implements Contents{
public int value() { return 1; }
}
下载源码就到源码网,
c
源码网资料下载:
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
}
}
3.2 static内隐类
1) 除了可用产生non-static内隐类对象的方法来产生之外,也可以不通过已存在一个enc下载源码就到源码网,
源码网资料下载:
losing 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对象来产生
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 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(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()"); }
}
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()
Egg.Yolk()
在(2)中我们尝试覆写inner class。当通过(3)产生一个BigEgg时,会调用Egg的构下载源码就到源码网,
源码网资料下载:
造函数。在Egg的构造函数的(1)处产生的是Egg.Yolk class对象,而不是子类BigEgg.Yolk class对象。
第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(Map m){ System.out.print("Size = " + m.size()); System.out.println(" , Keys: " + m.keySet()); }
public static void printValues(Map m){
System.out.println("Values: " + m.values()); }
public static void test(Map m){
for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
printKeys(m);
printValues(m);
System.out.println("km1 - " + m.get("km1")); Set keys = m.keySet(); //(1)
Collection values = m.values(); //(2) keys.remove("km2"); //(3)
values.remove("m1"); //(4)
System.out.println("km1 - " + m.get("km1")); printKeys(m);
printValues(m);
下载源码就到源码网,
源码网资料下载:
}
public static void main(String[] args){
System.out.println("Testing HashMap");
test(new 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 Map.Entry, Comparable{
Object key, value; //key和value分别用来存放Map中的key和value MPair(Object k, Object v){
key = k;
value = v;
}
//下面方法实现了Map.Entry接口中的方法
public Object getKey() { return key; }
public Object getValue() { return value; }
public Object setValue(Object v){
Object result = value;
value = v;
return result;
}
//下面方法实现了Comparable接口中的方法
public boolean equals(Object o){
return key.equals(((MPair)o).key);
下载源码就到源码网,
源码网资料下载:
}
public int compareTo(Object rv){
return ((Comparable)key).compareTo(((MPair)rv).key); }
}
class SlowMap extends AbstractMap{
private ArrayList
keys = new ArrayList(),
values = new ArrayList();
public Object put(Object key, Object value){
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 Object get(Object key){
if(!keys.contains(key)){
return null;
下载源码就到源码网,
源码网资料下载:
}
else
return values.get(keys.indexOf(key)); }
//用Mpair封装Map中的key-value pair并存入Set中 public Set entrySet(){
Set entries = new HashSet();
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(String[] args){
SlowMap m = new SlowMap();
for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
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。
关于Map的应用的一点补充:
三. HashMap的工作原理及实现
下载源码就到源码网,
源码网资料下载:
1.3 如何对Map用迭代器进行操作
其它容器可以通过iterator()函数来生成对象的迭代器,但Map是不能生成的。如果要用迭代器对Map进行操作,则要通过entrySet()函数。用entrySet()函数生成的迭代器不能对Map进行add和addAll的操作。
public class ExplicitStatic{
public static void main(String[] args){
HashMap m = new HashMap();
for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
System.out.println("User for loop:");
for( int i=1; i<=m.size(); i++ )
System.out.println("km" + i + " = " + m.get("km" + i));
System.out.println("User Iterator loop:");
Iterator it = m.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = (Map.Entry)it.next();
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
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 Map.Entry, Comparable{
下载源码就到源码网,
源码网资料下载:
Object key, value;
MPair(Object k, Object v){
key = k;
value = v;
}
public Object getKey() { return key; }
public Object getValue() { return value; }
public Object setValue(Object v){
Object result = value;
value = v;
return result;
}
/**
* 当比较两个MPair对象时,比较的是它们的key值 */
public boolean equals(Object o){
return key.equals(((MPair)o).key);
}
public int compareTo(Object rv){
return (((Comparable)key).compareTo(((MPair)rv).key)); }
}
下载源码就到源码网,
源码网资料下载:
class SimpleHashMap extends AbstractMap{
private final static int SZ = 997;
private LinkedList[] bucket = new LinkedList[SZ];
/**
* 把key和value封装成Map.Entry的实现类后插入到array中 */
public Object put(Object key, Object value){
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 LinkedList();
//通过hash code找出要插入的key所对应的array中的元素 LinkedList pairs = bucket[index];
//把要插入的key-value pair封装成MPair
MPair pair = new MPair(key, value);
ListIterator it = pairs.listIterator();
boolean found = false;
//检查是否有与要插入的key相同的key存在,如果有,就对之进行更新 while(it.hasNext()){
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 Object get(Object key){
int index = key.hashCode() % SZ; if(index < 0) index = -index;
if(bucket[index] == null) return null; LinkedList pairs = bucket[index]; ListIterator it = pairs.listIterator(); MPair match = new MPair(key, null); while(it.hasNext()){
Object iPair = it.next();
if(iPair.equals(match))
下载源码就到源码网,
源码网资料下载:
return ((MPair)iPair).getValue(); }
return null;
}
public Set entrySet(){
Set entries = new HashSet();
for(int i=0; i
if(bucket[i] == null) continue;
Iterator it = bucket[i].iterator();
while(it.hasNext())
entries.add(it.next());
}
return entries;
}
}
public class ExplicitStatic{
public static void main(String[] args){
SimpleHashMap m = new SimpleHashMap(); for( int i=1; i<10; i++)
m.put("km" + i, "m" + i);
System.out.println(m);
下载源码就到源码网,
源码网资料下载: }
}
四. HashMap的一些其它讨论
1. 关于HashMap中的key值的使用
1.1. 以Java的库函数做为HashMap的key值时,可以直接使用。 import java.util.*;
class Counter{
int i = 1;
public String toString(){
return Integer.toString(i);
}
}
public class ExplicitStatic{
public static void main(String[] args){
HashMap hm = new HashMap();
for(int i = 0; i < 10000; i++)
{
//HashMap的key的类型为Integer
Integer r = new Integer((int) (Math.random() * 20)); if(hm.containsKey(r))
((Counter)hm.get(r)).i++;
下载源码就到源码网,
源码网资料下载:
else
hm.put(r, new Counter());
}
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 String toString(){
return "Groundhog@" + ghNumber;
}
}
class Prediction{
boolean shadow = Math.random() > 0.5;
public String toString(){
if(shadow)
下载源码就到源码网,
源码网资料下载:
return "Six more weeks of Winter!\n";
else
return "Early Spring!\n";
}
}
public class Test{
public static void main(String[] args){
HashMap hm = new HashMap();
for(int i = 1; i < 10; i++)
hm.put(new Groundhog(i), new Prediction());
System.out.println("hm = " + hm);
System.out.println("Looking up prediction for Groundhog #3:"); Groundhog gh = new Groundhog(3);
if(hm.containsKey(gh)) //(1)
System.out.println((Prediction)hm.get(gh));
else
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 String toString(){
return "Groundhog2@" + ghNumber;
下载源码就到源码网,
源码网资料下载:
}
/**
* 以ghNumber作为hash code
*/
public int hashCode() { return ghNumber; } /**
*比较的是两个key的ghNumber值
*/
public boolean equals(Object o)
{
return (o instanceof Groundhog2)
&& (ghNumber == ((Groundhog2)o).ghNumber); }
}
class Prediction{
boolean shadow = Math.random() > 0.5;
public String toString(){
if(shadow)
return "Six more weeks of Winter!\n"; else
return "Early Spring!\n";
}
下载源码就到源码网,
源码网资料下载:
}
public class Test{
public static void main(String[] args){
HashMap hm = new HashMap();
for(int i = 1; i < 10; i++)
hm.put(new Groundhog2(i), new Prediction());
System.out.println("size = " + hm.size() + " , hm = " + hm); System.out.println("Looking up prediction for Groundhog #3:"); Groundhog2 gh = new Groundhog2(2);
if(hm.containsKey(gh))
System.out.println((Prediction)hm.get(gh));
else
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。
第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.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){
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;
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)在异常规格中声明将抛出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"); //(1)
}
}
public class ExplicitStatic{
public static void main(String[] args){
Test1 te = new Test1();
try{
te.f();
}
catch(Exception ex){
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();
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()
printStackTrace打印出Throwable和其call stack trace。 FillStackTrace则在调用点设立新的stack 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(); (2) }
}
}
public class Test{
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
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){
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) {
System.out.println("10/" + x + " = " + 10/x);
}
}
public class Test{
public static void main(String[] args){
TestException.g(2);
TestException.g(0); //(1)
}
}
下载源码就到源码网,
源码网资料下载: 上面代码在编译时不会发生错误,只有在运行时(1)处会发生错误。虽然除法可能会存在错误,但我们不用进行捕捉,当发生错误时,Java会自动抛出相应异常。
三. 以finally进行清理
1. 如果某段代码不管是否发生异常都要执行,那可把它改入finally块中。
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("catch SQLException in f()");
throw ex; //(1)
}
finally{
System.out.println("finally in f()");
下载源码就到源码网,
源码网资料下载:
}
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main"); }
catch(Exception ex){
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.SQLException;
class TestException{
public static void tSql1() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql1");
}
public static void tSql2() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql2");
}
public void f() throws SQLException{
try{
tSql1();
}
catch(SQLException ex){
System.out.println("catch SQLException in f()");
throw ex; //(2)
下载源码就到源码网,
源码网资料下载:
}
finally{
System.out.println("finally in f()");
//tSql2(); (1)
}
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main"); System.out.println("getMessage:" + ex.getMessage()); 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.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(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.Exception;
class BaseException extends 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(String s) throws Derived1Exception, Derived2Exception{} //声明base class构造函数中的异常父类异常和新的异常
DerivedClass1(String s, int i) throws BaseException, Derived2Exception{} DerivedClass1(int i, int j) { super(i, j); }
//注意下面这两句
DerivedClass1(int i, 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(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) {}
}
}
这篇文章的一点补充:
1.2 异常处理的讨论
1.2.1 当try中的语句产生异常时,在异常处理函数捕捉了该异常而又没有重新抛出异常时,则在执行完处理函数后,将跳出发生异常的try块,接着执行下面的语句: 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("catch SQLException in f()"); //throw ex; (1)
}
System.out.println("after exception handle in f()"); //(2) }
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main"); }
catch(Exception ex){
下载源码就到源码网,
源码网资料下载:
System.out.println("catch te.f() Exception in main");
}
}
}
运行结果为:
Originating the exception in tSql()
catch SQLException in f()
after exception handle in f()
在f()函数中执行完异常处理函数后,将继续执行发生异常的try….catch外的代码(2)。
1.2.2 当try中的语句产生异常时,在异常处理函数捕捉了该异常而又重新抛出异常时,则将把异常抛出到上一层context中,发生异常的语句之后的代码将不执行。
如果去年上面代码中(1)处的注释,那么运行结果变为:
Originating the exception in tSql()
catch SQLException in f()
catch te.f() SQLException in main
由于在(1)处重新抛出了异常,所以(2)处代码没有被执行。
1.2.3 如果没处理相应异常的处理函数,将把异常抛出到上一层context中,发生异常的语句之后的代码将不执行。
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{
int x = 0;
System.out.println("10/" + x + " = " + 10/x); //(1) System.out.println("after exception handle in f()"); }
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main"); }
catch(Exception ex){
System.out.println("catch te.f() Exception in main"); }
}
}
下载源码就到源码网,
源码网资料下载:
运行结果为:
catch te.f() Exception in main
在代码(1)处发生了异常,但在f()函数中没有对它进行处理,所以它会被抛出到上一层context中并跳出f()函数。
下载源码就到源码网,