■ 关于包
包的定义必须是第一条语句,也就是在包的定义之前没有语句,但是可以是注释。
如下程序:
package
MyPackage;
class
Test
...
{
public static void main(String[] args)...{
System.out.println("Test a package!!!");
}
}
编译成功,但是运行出错:
如果尝试着执行下面语句:
java sky2098.MyPackage
仍然出错:
上面这两种错误信息是有区别的:
第一种:是找不到MyPackage类;
第二种:在包sky2098下找不到MyPackage.class文件。
解决上面错误的方法:
在目录Lesson3下建立一个sky2098目录,将MyPackage.class拷贝进去,然后再运行命令:
java sky2098.MyPackage
运行结果:
另外也可以运行命令:
java sky2098/MyPackage
运行结果:
注意:这种命令方式中,java shiyanjun/Test用的必须是“反斜杠”!!!
当我们声明包的时候,可以是多层限定的包:
package
sky2098.shiyanjun.keller;
class
MyPackage
...
{
public static void main(String[] args)...{
System.out.println("Test a package!!!");
}
}
可以通过运行命令:
java sky2098.shiyanjun.keller.MyPackage
运行结果:
如果用命令
java sky2098/shiyanjun/keller/MyPackage
必须先在磁盘上创建目录sky2098/shiyanjun/keller/,然后能够成功运行:
但是,用java MyPackage命令时不小心谨慎会出错的:
我们切换到目录D:/javae/Lesson3/sky2098/shiyanjun/keller>下,运行会出错。
这是因为我们已经在目录sky2098.shiyanjun.keller内部了,所以访问不到MyPackage类了。
然而,如果我们拥有成千上万的类,要在磁盘上创建目录,工作量会很大,Java提供了自动生成目录的命令,我们可以这样做:
java源文件为:
package
aa.bb.cc.test;
class
Test
...
{
public static void main(String[] args)...{
System.out.println("Test a package!!!");
}
}
在D:/javae/Lesson3目录下,编译后运行命令:
javac -d . Test.java
亦即 javac空格横d空格点空格Test点java
编译成功:
这样,就可以自动在我们的磁盘上创建目录D:/javae/Lesson3/aa/bb/cc/test:
这样,在比较大的项目开发中可以节省相当大的工作量。
关于javac的用法,可以查看DOS下的帮助:
通过帮助,我们也可以指定存放生成的类文件的路径,比如,在在D:/javae/Lesson3目录下,执行命令:
javac -d e:/java Test.java
亦即 javac空格横d空格e:/java空格Test点java
编译成功:
则在e:/java/目录下生成了目录aa/bb/cc/test,并且类文件Test.class存放在e:/java/aa/bb/cc/test目录下:
如果我们想要访问执行D:/java/aa/bb/cc/test目录下的Test.class文件,可以先设置classpath,在通过包访问:
但是这个运行的并不是我们实际想要运行的e:/java/aa/bb/cc/test目录下的Test.class文件。
因为我们设置classpath时%classpath%;表明了是当前目录,在当前目录下就已经找到Test.class文件,并成功运行了。
如果把当前目录d:/java/aa/bb/cc/test下的Test.class文件删除掉,就会出错:
也就是d:/java/aa/bb/cc/test目录下的不存在Test.class文件,所以无法访问。
因为在java文件中aa.bb.cc.test.Test是一个类,必须指明aa.bb.cc.test.Test的路径,即为E:/java,然后才能通过E:/javae/Lesson3>java aa.bb.cc.test.Test执行E:/java/aa/bb/cc/test目录下的Test.class文件。
设置classpath的命令为:
set classpath=%classpath%;e:/java;d:/java/aa/bb/cc/test;
编译运行:
如果我们切换到目录e:/java/aa下,然后运行命令:
set classpath=.
java aa.bb.cc.test.Test
则会出错:
这是因为我们已经在aa目录下了,通过java aa.bb.cc.test.Test执行e:/java/aa/bb/cc/test目录下的Test.class文件会失败。
我们想要访问到e:/java/aa/bb/cc/test目录下的Test.class文件,必须退回到a/bb/cc/test/的上一级目录后运行:
这样才能访问到e:/java/aa/bb/cc/test目录下的Test.class文件。
【疑问】如果我们想在e:/java/aa目录下访问执行e:/java/aa/bb/cc/test目录下的Test.class文件,应该如何访问呢?
我们可以通过D:/jdk1.5.0_06/jre/lib目录下的rt.jar文件,用WinRAR打开可以看到Sun公司为我们提供的包的组织形式:
其中有sunw,sun,org,META-INF,javax,java,com七个包,分别在这个七个目录下找到相应的类。
我们也可以反编译各个类文件(.class)查看java源文件(.java)。
■ 关于访问权限
同一个类中:public,protected,default,private修饰的方法都可以被访问;
同一个包中:public,protected,default修饰的方法都可以被访问,而private修饰的方法都不可以被访问;
类的子类中:public,protected修饰的方法都可以被访问,而default,private修饰的方法都不可以被访问;
不同的包中:public修饰的方法都可以被访问,而protected,default,private修饰的方法都不可以被访问。
■ 关于final
为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖(overridden),可以使用final方法。
为了效率上的考虑,将方法声明为final,让编译器对此方法的调用进行优化。要注意的是:编译器会自行对final方法进行判断,并决定是否进行优化。通常在方法的体积很小,而我们确实不希望它被覆盖时,才将它声明为final。
class中所有的private和static方法自然就是final。
■ 关于abstract
在类中没有方法体的方法,就是抽象方法。
含有抽象方法的类,即为抽象类。
如果一个子类没有实现抽象基类中所有的抽象方法,则子类也成为一个抽象类。
我们可以将一个没有任何抽象方法的类声明为abstract,避免由这个类产生任何的对象。
构造方法、静态方法、私有方法、final方法不能被声明为抽象的方法。
■ 关于native
native方法是用户在Java中可以使用,但不能编写的方法。
■ 关于JNI技术
JNI(Java Native Interface),它允许Java虚拟机(JVM)内部运行的Java代码能够与用其它编程语言(如C、C++、汇编语言)编写的应用程序和库进行互操作。
JNI最大的好处是它没有对底层Java虚拟机的实现施加任何限制,因此,Java虚拟机厂商可以在不影响虚拟机其它部分的情况下添加对JNI的支持。程序员只需编写一种版本的本地(Native)应用程序和库,就能够与所有支持JNI的Java虚拟机协同工作。
JNI可以理解为Java和本地应用程序之间的中介。
一般步骤:
1。先编写java源文件;
2。编译java文件;
3。用javah -jni命令编译头文件;
4。本地方法的实现,用非java语言,如C,C++等其他语言编写程序源文件;
5。创建一个共享库,用cl编译器编译;
6。执行java程序。
举例如下:
1。先编写java源文件HelloWorld.java:
class
HelloWorld
...
{
public native void displayHelloWorld();
static
...{
System.loadLibrary("hello");
}
public static void main(String[] args) ...{
new HelloWorld().displayHelloWorld();
}
}
2。编译HelloWorld.java文件:
3。再用如下命令编译:
javah -jni HelloWorld
则在当前目录D:/javae/Lesson3下生成HelloWorld.h头文件,文件内容为:
/**/
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
<
jni.h
>
/**/
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define
_Included_HelloWorld
#ifdef __cplusplus
extern
"
C
"
...
{
#endif
/**//*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
4。本地方法的实现,用c语言编写HelloWorldImp.c文件:
#include
<
jni.h
>
#include
"
HelloWorld.h
"
#include
<
stdio.h
>
JNIEXPORT
void
JNICALL
Java_HelloWorld_displayHelloWorld(JNIEnv
*
env, jobject obj)
...
{
printf("Hello world! ");
return;
}
将HelloWorldImp.c放到当前目录D:/javae/Lesson3下面。
5。创建一个共享库,用cl编译器,命令为:
cl -ID:/jdk1.5.0_06/include -ID:/jdk1.5.0_06/include/win32 -LD HelloWorldImp.c -FeHello.dll
编译结果:
这样就创建了动态连接库文件Hello.dll。
6。运行java程序:
运行结果:
■ cl编译器的用法
可以查看DOS命令帮助:
■ java中的垃圾收集器
示例程序如下:
class
Garbage
...
{
int index;
static int count;
Garbage()...{
count++;
System.out.println("object "+count+" construct");
setID(count);
}
void setID(int id)...{
index=id;
}
protected void finalize()...{
System.out.println("object "+index+" is reclaimed");
}
public static void main(String[] args)...{
new Garbage();
new Garbage();
new Garbage();
new Garbage();
System.gc();
}
}
编译运行:
如果不用System.gc();则运行结果不会显式地回收垃圾,它只有在内存不足等情况时才自动回收垃圾。
调用System.gc();可以显式地回收垃圾。
■ 关于接口(Interface)
编写一个接口程序:
interface
Sport
...
{
void run();
void jump();
}
class
Athlete
implements
Sport
...
{
void run()...{
System.out.println("Running!");
}
void jump()...{
System.out.println("Jumping");
}
public static void main(String[] args)...{
Athlete sky2098=new Athlete();
sky2098.run();
sky2098.jump();
}
}
编译出错:
我们在一个类实现一个接口声明的方法时,在类中实现方法的访问权限必须等于或者高于接口中声明的方法的访问权限。
修改上面程序:在Athlete类中将run和jump方法的访问权限声明为public,高于接口中的访问权限,然后编译运行:
注意:
编译时是接口名,接口是一种特殊的类;
运行时加载的是类,应该使用实现接口的类的类名。
■ 接口应用的一个综合实例
声明一个接口(VideoCard.java):
interface
VideoCard
...
{
void Display();
String getName();
}
编写Dmeng类,实现接口VideoCard:
class
Dmeng
implements
VideoCard
...
{
String name;
public Dmeng()...{
name="Dmeng VideoCard";
}
public void setName(String name)...{
this.name=name;
}
public void Display()...{
System.out.println("Dmeng's vediocard is working!");
}
public String getName()...{
return name;
}
}
编写MainBoard类,调用实现接口(VideoCard)的类(Dmeng):
class
MainBoard
...
{
String strCPU;
VideoCard vc;
void setCPU(String strCPU)...{
this.strCPU=strCPU;
}
void setVideoCard(VideoCard vc )...{
this.vc=vc;
}
void run()...{
System.out.println(strCPU);
System.out.println(vc.getName());
vc.Display();
System.out.println("MainBoard is running now!");
}
}
编写Computer类,调用Dmeng类,MainBoard类:
class
Computer
...
{
public static void main(String[] args)...{
Dmeng d=new Dmeng();
MainBoard m=new MainBoard();
m.setCPU("Intel's CPU");
m.setVideoCard(d);
m.run();
}
}
编译运行:
注意:我们在编写接口以及类的时候,有多个类文件,如果单个编译可能有先后顺序,所以可以这样编译:
javac *.java
这样,会对当前目录下的所有java源文件进行编译,而不会出现因为文件编译的先后顺序造成的错误。
在Java中,一个类可以实现多个接口。
一个类在继承另外一个类的同时,可以实现多个接口。
一个类可以既继承一个抽象类又实现一个接口,注意次序:先用extends,再用implements。
■ 关于内部类
▲ 内部类概要
在一个类中定义另外一个类,这个类就叫做内部类或内置类 (inner class) 。
内部类可以让我们将逻辑上相关的一组类组织起来,并由外部类(outer class)来控制内部类的可见性。
当我们建立一个inner class时,其对象就拥有了与外部类对象之间的一种关系,这是通过一个特殊的this reference形成的,使得内部类对象可以随意的访问外部类中所有的成员。
编写一个内部类:
class
Outer
...
{
private int index=10000;
class Inner...{ //Inner类是一个内部类
void print()...{
System.out.println(index);
}
}
void print()...{
Inner inner=new Inner();
inner.print();
}
}
class
TestInnerClass
...
{
public static void main(String[] args)...{
Outer outer=new Outer();
outer.print();
}
}
编译运行:
我们查看当前目录D:/javae/Lesson3,可以看到存在一个名称为Outer$Inner.class的文件,这个标识的就是内部类的类文件。
在内部类的方法中引用外部类、内部类、方法体中的成员(成员变量和成员方法)或变量,实现方法:
class
Outer
...
{
private int index=10000;
class Inner...{
private int index=3000;
void print()...{
int index=500;
System.out.println(index); //引用方法体内部的变量index
System.out.println(this.index); //引用内部类的成员变量index
System.out.println(Outer.this.index); //引用外部类的成员变量index
}
}
void print()...{
Inner inner=new Inner();
inner.print();
}
Inner getInner()...{
return new Inner();
}
}
class
TestInnerClass
...
{
public static void main(String[] args)...{
Outer outer=new Outer();
System.out.println("--Inner.print();--");
Outer.Inner inner=outer.getInner(); //在Outer类外部访问Inner类必须指明外部类Outer
inner.print();
System.out.println("--outer.print();--");
outer.print();
}
}
编译运行:
如果把main主函数放到Outer类中,则定义Inner类变量就不用指定外部类Outer了:
如果在main主函数中直接创建一个Inner类的实例:
Inner inner=new Inner();
编译会出现错误,可以这样有两种理解方式:
一种理解为:可以把类Inner看作是类Outer的一个非静态成员,而main主函数是静态的方法,不可以在静态方法中访问非静态成员。
另外一种理解为:因为内部类可以随意访问外部类的成员,如果外部类没有创建实例,那么内部类就不能够访问外部类的成员。
如果想要用new创建Inner类实例,则可以在main主函数中这样实现:
Outer.Inner inner=Outer.new Inner();
编译不会出错。
可见,内部类的对象是与外部类相关联的。
我们也可以在外部类的一个成员方法中定义内部类,或者作为一个语句块,但是这样就限定了内部类的使用范围,只能在方法体的内部被访问,或者只能作为一个语句块。
一个内部类,无论在外部类中嵌套的层次如何,它都可以访问外部类的成员!
内部类可以嵌套在另一个内部类中,这样对于访问外部类的成员提供了很大的方便。
如果一个内部类在一个外部类的方法内部,想要访问该方法的参数或者该方法的中的其他变量,必须将参数和变量声明为final的;但是如果内部类没有访问该方法的参数或者该方法的中的其他变量,参数和变量就不是必须声明为final的。
内部类有四种访问权限:public,protected,default,private,相当与一个类的成员方法。
内部类可以声明为abstract的,可以在外部类中再重新定义一个内部类实现抽象的内部类,也可以在外部类之外实现这个外部类的一个抽象内部类。
内部类也可以声明为final的。
当内部类声明为static的时候,在访问内部类的时候就不用必须指定外部类,当然也就不能访问外部类的非静态成员了。
如果想要访问外部类的静态成员,只需要用外部类的类名即可,而不用this,比如:
class
Outer
...
{
private static int index=10000; //index声明为静态成员变量
static class Inner...{ //Inner定义为静态内部类
private int index=3000;
void print()...{
int index=500;
System.out.println(index); //引用方法体内部的变量index
System.out.println(this.index); //引用内部类的成员变量index
System.out.println(Outer.index); //引用外部类的成员变量index只要用Outer.index即可,而不用Outer.this.index了
}
}
void print()...{
Inner inner=new Inner();
inner.print();
}
Inner getInner()...{
return new Inner();
}
}
如果内部类不是静态内部类,就不能在内部类中定义静态成员;静态内部类中可以定义静态成员。
一个类要继承一个内部类,实现方法:
class
Car
...
{
class Wheel...{
}
}
class
PanelWheel
extends
Car.Wheel
...
{
PanelWheel(Car car)...{
car.super();
}
public static void main(String[] args)...{
Car car=new Car();
PanelWheel pw=new PanelWheel(car);
}
}
一个类(PanelWheel)要继承一个内部类(Wheel),首先应该建立内部类和外部类的一种引用关系。
首先在类PanelWheel的构造函数中建立一个内部类到一个外部类的一个引用关系,用car.super();这样的方式调用;
然后在main函数中实例化一个外部类(Car)的对象,使得类(PanelWheel)能够继承内部类(Wheel)而实例化一个对象。
当某个类要实现一个接口并继承另一个类,而这个接口和父类有相同的方法,则可以通过定义内部类来完成预期的功能:
实例程序代码如下:
interface
Machine
...
{
void run();
}
class
Person
...
{
void run()...{
System.out.println("Running now!");
}
}
class
RobotPerson
extends
Person
...
{
class MachineHeart implements Machine...{
public void run()...{
System.out.println("Heart running now!");
}
}
Machine getMachine()...{
return new MachineHeart();
}
}
class
TestMachine
...
{
public static void main(String[] args)...{
RobotPerson robot=new RobotPerson();
Machine machine=robot.getMachine();
machine.run();
robot.run();
}
}
编译运行:
▲ 内部类实现接口
编写一个实现接口的内部类:
interface
Animal
...
{
void eat();
void sleep();
}
class
Zoo
...
{
class Tiger implements Animal...{ //实现接口的内部类
public void eat()...{
System.out.println("Tiger eat!");
}
public void sleep()...{
System.out.println("Tiger sleep!");
}
}
Animal getAnimal()...{
return new Tiger();
}
}
class
TestAnimal
...
{
public static void main(String[] args)...{
Zoo z=new Zoo();
Animal an=z.getAnimal();
an.eat();
an.sleep();
}
}
编译运行:
▲ 匿名的内部类
编写一个匿名的内部类的程序:
interface
Animal
...
{
void eat();
void sleep();
}
class
Zoo
...
{
class Tiger implements Animal...{
public void eat()...{
System.out.println("Tiger eat!");
}
public void sleep()...{
System.out.println("Tiger sleep!");
}
}
Animal getAnimal()...{
return new Animal()...{
public void eat()...{
System.out.println("Animal eat!");
}
public void sleep()...{
System.out.println("Animal sleep!");
}
};
}
}
class
TestAnimal
...
{
public static void main(String[] args)...{
Zoo z=new Zoo();
Animal an=z.getAnimal();
an.eat();
an.sleep();
}
}
编译运行:
在Zoo类的getAnimal()方法中,我们用return new Animal()返回一个Animal接口类的对象。
但是由于Animal是一个接口,我们首先要实现这个接口,然后才能够实例化一个接口类对象。
所以,在return new Animal()中的方法体就是一个匿名类,上面的java程序完全可以写为:
interface
Animal
...
{
void eat();
void sleep();
}
class
Zoo
...
{
class Tiger implements Animal...{
public void eat()...{
System.out.println("Tiger eat!");
}
public void sleep()...{
System.out.println("Tiger sleep!");
}
}
Animal getAnimal()...{
class AA implements Animal...{
public void eat()...{
System.out.println("Animal eat!");
}
public void sleep()...{
System.out.println("Animal sleep!");
}
}
return new AA();
}
}
class
TestAnimal
...
{
public static void main(String[] args)...{
Zoo z=new Zoo();
Animal an=z.getAnimal();
an.eat();
an.sleep();
}
}
编译运行:
运行结果同上面相同。
匿名的内部类也可以实现抽象类。
▲ 内部类总结
1、在内部类(inner class)中,可以随意的访问外部类的成员,这可以让我们更好地组织管理我们的代码,增强代码的可读性。
2、内部类可以用于创建适配器类,适配器类是用于实现接口的类。使用内部类来实现接口,可以更好地定位与接口关联的方法在代码中的位置。
3、内部类的更多用法。
4、用户通过接口访问类,使用内部类可以隐藏实现的细节。
■ 关于异常
▲ 异常类概述
打开一个不存在的文件、网络连接中断、数组下标越界、正在加载的类文件丢失等都会引发异常。
Java中的异常类定义了程序中遇到的轻微的错误条件。
Java中的错误类定义了程序中不能恢复的严重错误条件。如内存溢出、类文件格式错误等。这一类错误由Java运行系统处理,不需要我们去处理。
Java程序在执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
当Java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。
如果Java运行时系统找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出。
如果某个语句发生异常,则它后面的语句将不会被执行,所以一般用try...catch来捕获处理异常。完整的语句为
try...catch...finally
▲ 一个除法异常的实例
程序代码如下:
class
MyException
...
{
int Divide(int a,int b)...{
return a/b;
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,0);
}catch(Exception e)...{
System.out.println(e.getMessage());
}
}
}
编译运行:
处理异常输出的格式有很多种,详细程度不同:
如果把System.out.println(e.getMessage());改写成为:
System.out.println(e.toString());
重新编译运行:
如果把System.out.println(e.getMessage());改写成为:
e.printStackTrace();
重新编译运行:
其中,最后一种方式最详细,个跟踪了异常发生的轨迹,便于调试。
▲ 异常的抛出以及捕捉处理
我们也可以在被调用的方法中对异常进行捕捉处理:
class
MyException
...
{
int Divide(int a,int b)...{
try...{
return a/b;
}catch(Exception e)...{
e.printStackTrace();
}
return 0; //发生异常后返回int型值
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
exception.Divide(12,0);
}
}
编译运行:
与上面的运行结果相同。
注意:
如果在Divide方法中没有语句return 0;则编译会出错,因为语句exception.Divide(12,0);已经发生异常,没有实现返回int型的值,而Divide要求返回值是int型,这就是错误的原因所在。
如果我们不想在编写方法时对可能发生的异常进行捕获处理,则可以用throws Exception来向调用这个方法的用户抛出异常,让用户自己对异常进行捕获处理:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{ //抛出Exception异常
return a/b;
}
int D1(int a1,int b1)...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
exception.D1(12,0);
}
}
编译结果:
程序运行抛出异常,然后可以由用户对发生的异常进行捕捉处理。
当然,如果用户也不想对异常进行捕获处理,也可以继续抛出,如下代码所示:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{ //对异常不进行处理,抛出Exception异常
return a/b;
}
int D1(int a1,int b1)throws Exception...{ //对异常不进行处理,抛出Exception异常
return Divide(a1,b1);
}
int D2(int a2,int b2)throws Exception...{ //对异常不进行处理,抛出Exception异常
return D1(a2,b2);
}
int D3(int a3,int b3)throws Exception...{ //对异常不进行处理,抛出Exception异常
return D2(a3,b3);
}
int Dn(int an,int bn)throws Exception...{ //对异常不进行处理,抛出Exception异常
return D3(an,bn);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
exception.Dn(12,0);
}
}
编译结果:
我们还可以通过main函数将异常抛给运行时系统,程序代码如下所示:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{
return a/b;
}
int D1(int a1,int b1)throws Exception...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)throws Exception...{ //主函数抛出异常
MyException exception=new MyException();
exception.D1(12,0);
}
}
编译成功,运行结果:
通过运行结果可见,运行时系统调用了printStackTrace()方法进行异常捕捉处理。
如果抛出Exception异常,我们也可以用Exception的子类ArithmeticException来捕捉异常,程序代码如下:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{
return a/b;
}
int D1(int a1,int b1)throws Exception...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)throws Exception...{
MyException exception=new MyException();
try...{
exception.D1(12,0);
}catch(ArithmeticException e)...{ //ArithmeticException异常类是Exception异常类的子类
System.out.println(e.toString());
}
}
}
编译运行:
我们能够用多个catch进行捕获异常,程序代码如下:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{
return a/b;
}
int D1(int a1,int b1)throws Exception...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)throws Exception...{
MyException exception=new MyException();
try...{
exception.D1(12,0);
}catch(ArithmeticException e)...{ //用ArithmeticException异常类进行捕获处理
System.out.println(e.toString());
}
catch(Exception e)...{ //用Exception异常类进行捕获处理
e.printStackTrace();
}
}
}
编译运行:
这里,先用Exception的派生类ArithmeticException进行捕获,再用Exception来捕获,实现了异常的捕获处理。
但是如果我们先用Exception进行捕获,再用Exception的派生类ArithmeticException进行捕获:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{
return a/b;
}
int D1(int a1,int b1)throws Exception...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)throws Exception...{
MyException exception=new MyException();
try...{
exception.D1(12,0);
}catch(Exception e)...{ //先用Exception进行捕获
e.printStackTrace();
}
catch(ArithmeticException e)...{ //再用Exception的派生类ArithmeticException进行捕获
System.out.println(e.toString());
}
}
}
则编译会出错:
也就是说,用Exception类已经将所有可能抛出的异常捕获了,不需要再用ArithmeticException来捕获异常了。
对异常进行捕获,我们一般先用具体的异常类来捕获异常,然后用范围更大的异常类来捕获异常。
另外一种捕捉异常的方法:
是在捕捉异常的catch中用throw来捕捉异常:
class
MyException
...
{
int Divide(int a,int b)throws Exception...{
return a/b;
}
int D1(int a1,int b1)throws Exception...{
return Divide(a1,b1);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)throws Exception...{
MyException exception=new MyException();
try...{
exception.D1(12,0);
}catch(Exception e)...{
e.printStackTrace();
throw new Exception("Cannot divided by zero!"); //用throw来捕捉异常
}
}
}
编译运行:
也实现了对异常的捕捉处理。
抛出异常的时候,可以抛出多个异常,用“,”分隔各个异常类;
当然,也可以自己定义异常类,自定义的异常类必须继承Exception类,如:
class
MyException
...
{
int Divide(int a,int b)throws Exception,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-3);
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
}catch(Exception e)...{
e.printStackTrace();
}
}
}
编译运行:
▲ 关于try...catch...finally
无论是否发生异常,finally中的语句都执行。
下面重点通过程序来说明finally的用法和功能:
class
MyException
...
{
int Divide(int a,int b)throws Exception,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-3);
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
}catch(Exception e)...{
e.printStackTrace();
}
finally...{
System.out.println("finally!");
}
System.out.println("finishing!");
}
}
编译运行:
显然,异常发生了,但是finally中的语句执行了,输出字符串"finally!"。而在try...catch...finally之外的语句:
System.out.println("finishing!");
也能够执行到,输出字符串"finishing!"。
但是如果在catch中发生异常,我们希望,发生异常后应该返回,不再继续执行,这时System.out.println("finishing!");就不能够执行了,而finally中的语句仍然能够执行,如:
class
MyException
...
{
int Divide(int a,int b)throws Exception,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-2); //发生异常
return; //返回
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
return; //处理异常时返回
}catch(Exception e)...{
e.printStackTrace();
}
finally...{
System.out.println("finally!");
}
System.out.println("finishing!");
}
}
编译运行:
语句System.out.println("finishing!");没有执行,而finally中语句正常执行,输出字符串"finally!"。
如果让finally中语句也不能够执行,应该如何实现呢?
这是能够做到的,程序如下:
class
MyException
...
{
int Divide(int a,int b)throws Exception,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-2);
return;
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
System.exit(-1); //-1为非0值,表示异常终止,终止当前的java虚拟机
}catch(Exception e)...{
e.printStackTrace();
}
finally...{
System.out.println("finally!");
}
System.out.println("finishing!");
}
}
编译运行:
我们可以看到,所以finally中的语句也就没有得到执行。
程序中System.exit(-1); 表示终止当前正在运行的java虚拟机,只要System.exit(status);中status为负值时都表示终止当前正在运行的java虚拟机。
如果此时没有异常发生,我们把程序中exception.Divide(12,-2);改写为:
exception.Divide(12,1);
则没有异常发生,则可以输出字符串"finally!":
对于RuntimeException,通常不需要我们去捕获,这类异常由Java运行系统自动抛出并自动处理。
如果父类中的方法抛出多个异常,则子类中的覆盖方法要么抛出相同的异常,要么抛出异常的子类,但不能抛出新的异常。
注意:构造方法是除外的。
示例程序如下:
import
java.io.
*
;
class
MyException
...
{
MyException()...{
}
int Divide(int a,int b)throws ArithmeticException,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
MyChildException
extends
MyException
...
{ //MyChildException类继承了MyException类
int Divide(int a,int b)throws FileNotFoundException,DivisorIsMinusException...{ //MyChildException类覆盖了MyException类的Divide()方法,但是抛出了FileNotFoundException异常类
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-2);
}catch(ArithmeticException e)...{
e.printStackTrace();
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
}
finally...{
System.out.println("finally!");
}
System.out.println("finishing!");
}
}
编译出错:
如果想要编译成功,必须让MyChildException类的方法Divide()抛出的异常类是MyException类中方法Divide()的一个子集。
也就是,让MyChildException类的方法Divide()只抛出ArithmeticException或者DivisorIsMinusException或者两者都都抛出。
因为MyException类中方法Divide()方法只抛出了ArithmeticException,DivisorIsMinusException这两个异常类。
FileNotFoundException类是java.io包中的异常类,必须用import将其包含进去。
然而,构造方法是例外的,子类的构造方法抛出的异常类可以与父类构造方法的不同,举例如下:
import
java.io.
*
;
class
MyException
...
{
MyException()throws ArithmeticException...{ //父类MyException的构造方法抛出ArithmeticException异常类
}
int Divide(int a,int b)throws ArithmeticException,DivisorIsMinusException...{
if(b<0)...{
throw new DivisorIsMinusException("Divisor cannot be minus!");
}
return a/b;
}
}
class
MyChildException
extends
MyException
...
{
MyChildException()throws FileNotFoundException,DivisorIsMinusException...{ //而子类MyChildException的构造方法抛出FileNotFoundException,DivisorIsMinusException两个异常类
}
int Divide(int a,int b)throws ArithmeticException,DivisorIsMinusException...{
return a/b;
}
}
class
DivisorIsMinusException
extends
Exception
...
{
DivisorIsMinusException(String str)...{
super(str);
}
}
class
MyExceptionTest
...
{
public static void main(String[] args)...{
MyException exception=new MyException();
try...{
exception.Divide(12,-2);
}catch(ArithmeticException e)...{
e.printStackTrace();
}catch(DivisorIsMinusException e)...{
System.out.println(e.toString());
}
finally...{
System.out.println("finally!");
}
System.out.println("finishing!");
}
}
编译运行:
我们可以在方法声明时,声明一个不会抛出的异常,Java编译器就会强迫方法的使用者对异常进行处理。这种方式通常应用于abstract 在interface中。
■ Java编程规范
▲ package的命名
package 的名字由全部小写的字母组成,例如:cn.mybole。
▲ class和interface的命名
class和interface的名字由大写字母开头而其他字母都小写的单词组成,例如:Person,RuntimeException。
▲ class变量的命名
变量的名字用一个小写字母开头,后面的单词用大写字母开头,例如:index,currentImage。
▲ class方法的命名
方法的名字用一个小写字母开头,后面的单词用大写字母开头,例如:run(),getBalance()。
▲ static final变量的命名
static final变量的名字所有字母都大写,并且能表示完整含义。例如:PI,PASSWORD。
▲ 参数的命名
参数的名字和变量的命名规范一致。
▲ 数组的命名
数组应该总是用这样的方式来命名:byte[] buffer。
■ JVM 规格描述
▲ JVM定义了控制Java代码解释执行和具体实现的五种规格
○JVM指令系统
JVM指令系统同其他计算机的指令系统极其相似。Java指令也是由操作码和操作数两部分组成。操作码为8位二进制数,操作数紧随在操作码的后面,其长度根据需要而不同。操作码用于指定一条指令操作的性质(在这里我们采用汇编符号的形式进行说明),如iload表示从存储器中装入一个整数,anewarray表示为一个新数组分配空间,iand表示两个整数的“与”,ret用于流程控制,表示从对某一方法的调用中返回。当长度大于8位时,操作数被分为两个以上字节存放。JVM采用了“big endian”的编码方式来处理这种情况,即高位bits存放在低字节中。这同 Motorola及其他的RISC CPU采用的编码方式是一致的,而与Intel采用的“little endian ”的编码方式即低位bits存放在低位字节的方法不同。
Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多线程系统的指令。
○JVM寄存器
所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组。如果虚拟机定义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用常规存储器模拟寄存器,这反而会降低虚拟机的效率。针对这种情况,JVM只设置了4个最为常用的寄存器。它们是:
※pc 程序计数器
※optop 操作数栈顶指针
※frame 当前执行环境指针
※vars 指向当前执行环境中第一个局部变量的指针
所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。
○JVM栈结构
作为基于栈结构的计算机,Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:
※ 局部变量
局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。
※ 执行环境
执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环境是执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。
※ 操作数栈
操作数栈用于存储运算所需操作数及运算的结果。
○JVM碎片回收堆
Java类的实例(对象)所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。
在Java语言中,除了new语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由Java运行系统承担的。这允许Java运行系统的设计者自己决定碎片回收的方法。在SUN公司开发的Java解释器和Hot Java环境中,碎片回收用后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。
○JVM存储区
JVM有两类存储区:
常量缓冲池和方法区。
常量缓冲池用于存储类名称、方法和字段名称以及串常量。
方法区则用于存储Java方法的字节码。
对于这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。