1.什么是异常
异常由来:问题也是显示生活中一个具体的事物,也可以通过java类的形式进行描述,并封装成对象。 java分为两种问题,一种是严重的问题,一种是非严重的问题。 对于严重的,java通过Error类进行描述。 对于Error一般不编写针对性的代码对其进行处理。 对于非严重的,java通过Exception类进行描述。也就是异常。 无论Error或者Exception都具有一些共性内容。 比如:不正常情况的信息,引发原因等。
异常:就是程序在运行时出现的不正常情况。
异常的体系:
Throwable
|----Error |----Exception|----RuntimeException
2.异常的处理
java提供了特有的语句进行处理
try {
需要被检测的代码
} catch {
处理异常的代码
} finnally {
一定会执行的语句
}
例子1
class Demo {
int div(int a, int b) {
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
int x = d.div(4, 0);
System.out.println("x=" + x);
System.out.println("over");
}
}
上面代码的运行结果是:
首先我们要知道java虚拟机自带了异常处理机制,当我们除0之后,java虚拟机可以识别到这个异常,但是虚拟机处理的方式简单粗暴,直接输出异常信息,并且程序停止运行,出现异常的语句后面的代码都不会被执行。
下面我们给给程序加上自己的异常处理
class Demo {
int div(int a, int b) {
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
try {
int x = d.div(4, 0);
System.out.println("x=" + x);
} catch (Exception e) {
System.out.println("不能除0");
System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();
}
System.out.println("over");
}
}
运行结果:
可以看到over输出了,说明程序运行到了最后输出语句,那么异常处理的过程到底是怎样的,下面来分析一下。
1.首先程序运行到d.div(4,0)那里,调用Demo类的div函数,在函数中执行4/0,这时程序发生了不能除0异常,这个异常已经被java事先定义好了,就是ArithmeticException,在这里会把这个ArithmeticException封装成一个对象,相当与new ArithmeticException()。
2.发生这个异常的Demo类的div并没有处理异常的能力,它就把这个异常抛给了调用它的函数,也就是主函数,在主函数的try代码块了就有了这个算术异常,而try就是用来检测异常的,当这个异常被检测到之后,就会把它丢给了catch。
3.那么这个catch怎么接呢,可以看到catch后面定义了(Exception e)这个参数,因此catch接受到异常后其实相当于这样,Exception e = new ArithmeticException(), ArithmeticException是Exception的子类。然后就开始执行catch里面的代码。并且后面的程序也可以正常执行了。
P.S:1.如果在第2步当中,div函数把异常抛给了主函数之后,主函数也没有try-catch来处理这个异常,那么这个异常就会被主函数继续往上抛,抛给我们的虚拟机,虚拟机会停止程序,默认输出异常信息,也就是第一个例子。
2.在try代码块里,出现异常后面的语句是不会执行了,比如上面的System.out.println("x=" + x);
3.在这个例子中的catch打印那么多信息是为了让大家知道e.printStackTrace();到底是怎么构成的,可以看到e.getMessage只输出了异常信息,etoString输出了异常名称和异常信息,而e.printStackTrace最完整,包括了异常名称,异常信息,还有异常发生的位置,java虚拟机默认处理其实就是输出它。
例子2
上面那个例子存在一个问题,如果Demo是别人写的代码,我们调用里面的函数其实并不知道它是否会报异常,只有运行之后出错了才知道有异常,这是非常不科学的,所以我们必须在写代码的时候就的预判是否会发生异常,如果可能发生异常就声明异常,告诉调用者这代码有可能会发生异常,我们看下面的代码
class Demo {
int div(int a, int b) throws Exception{
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
int x = d.div(4, 0);
System.out.println("x=" + x);
System.out.println("over");
}
}
我们先把主函数中的try-catch语句去掉,然后在Demo类中的div函数声明异常,throws Exception,这代表这个函数有可能会发生异常,希望调用他的人能够处理一下。在Eclipse中,主函数div那里会飘红,它希望你给div加上异常处理,如果在控制台编译的会发生下面的错误
也就是说,如果你在某函数上声明异常,调用这个函数的人可以有两种解决办法,一种是用try-catch进行捕获,自己写代码解决,另外一种是调用的人继续将这个异常抛出,也就是在主函数上也加上throws Exception,最终这个异常会被抛给java虚拟机,让程序停止运行并输出错误信息。
例子3:多异常处理
例子2的异常声明并不规范,在平时写代码的时候我们不能笼统的将异常定义为Exception,必须具体问题具体处理,看代码
class Demo {
int div(int a, int b) throws ArithmeticException,ArrayIndexOutOfBoundsException{
int[] arr = new int[a];
System.out.println(arr[4]);
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
try{
int x = d.div(4, 0);
System.out.println("x=" + x);
} catch (ArithmeticException e) {
System.out.println("不能除以0");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("越界了");
}/* catch (Exception e) {
System.out.println(e.toString());
}*/
System.out.println("over");
}
}
抛出几个异常,我们就catch几个异常,分别处理,不要定义多余的catch块,另外需要注意的是,如果多个catch块中出现异常的继承关系,父类catch块放在下面,否则子类就没有存在的意义。比如把上面的Exception提到前面,那么算术异常和角标越界异常都无效了,因为Exception都可以处理,这种做法不提倡。还有最好不要在最后加上Exception,这样虽然当函数出现我们声明之外的异常之后,程序也可以运行,但是你无法确定知道该问题是什么,相当于你把问题隐藏起来了,会造成一定的隐患,所以宁愿开发的时候让程序停止报错,找出问题所在去处理。
例子4:自定义异常
class FuShuException extends Exception{
private int value;
FuShuException() {
super();
}
FuShuException(String msg, int value) {
super(msg);
this.value = value;
}
public int getValue() {
return value;
}
}
class Demo {
int div(int a, int b) throws FuShuException{
if(b < 0)
throw new FuShuException("出现除数是负数的情况", b);
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
try {
int x = d.div(4, -1);
System.out.println("x=" + x);
} catch (FuShuException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("错误的负数是" + e.getValue());
}
System.out.println("over");
}
}
3.自定义异常的有参构造函数是为了加上自己描述的异常信息,由于FuShuException继承了Exception,所以不必自己写函数输出异常描述,调用父类的函数就可以了。除了异常的描述,我们还可以获取我们需要的信息,比如上面的代码还获取到底出现了哪个负数。
例子5:RuntimeException
class Demo {
int div(int a, int b) {
if(b < 0)
throw new ArithmeticException(); //编译通过
// throw new Exception(); //编译失败
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
int x = d.div(4, 0);
System.out.println("x=" + x);
System.out.println("over");
}
}
之所以不用在函数上进行声明,是因为不需要让调用者处理,当该异常发生,希望程序停止,对代码进行修正。
暂且不论wait函数是干嘛用的,我们看到wait函数有三个异常,但是函数上只声明了一个,其另外两个就是运行时异常。我们再看它的参数,是时间。如果传了一个负数的时间,这程序根本没有必要继续运行下去,出现这种情况,我们不应该把这个异常给处理掉,而应该让程序停止,修改程序,传入一个正确的时间。
所以异常分两种,
1.编译时被检测的异常
该异常在编译时,如果没有处理(没有抛也没有try),编译失败
该异常被标识,代表这可以被处理
2.运行时异常(编译时不检测)
在编译时,不需要处理,编译器不检查
该异常的发生,建议不处理,让程序停止,需要对代码进行修改
例子6:try-catch-finally
<pre name="code" class="java">class FuShuException extends Exception{
private int value;
FuShuException(String msg, int value) {
super(msg);
this.value = value;
}
public int getValue() {
return value;
}
}
class Demo {
int div(int a, int b)throws FuShuException{
if(b < 0)
throw new FuShuException("出现负数了", b);
return a/b;
}
}
public class ExceptionDemo {
public static void main (String[] args) {
Demo d = new Demo();
try {
int x = d.div(4, -1);
System.out.println("x=" + x);
} catch (FuShuException e) {
System.out.println(e.toString());
return;
//System.exit(0); //这个例外,这代表jvm退出,finally不会执行
} finally {
System.out.println("finally");
}
System.out.println("over");
}
}
例子7:异常在子父类重写时的体现
1.子类在重写父类时,如果父类方法抛出异常,那么子类重写的方法,只能抛出父类的异常,或者该异常的子类。
2.如果父类方法抛出多个异常,那么子类在重写该方法时,只能抛出父类异常的子集。
3.如果父类或者接口的方法中没有抛出异常,那么在子类重写方法时,也不可以抛出异常。
如果子类方法发生了异常,就必须要进行try处理,绝对不能抛。