目录
引言:
异常,可能是我们写程序的过程中最不希望出现的东西了,谁不希望自己的程序尽善尽美呢,但程序你写出来是要为别人使用来创造价值的,在这个过程中,难免可能会出现一些不尽如人意的情况,接下来我们就来说说。
异常的概述与异常的分类:
java中对异常也进行了分类处理,那么它都有哪些分类呢?
我们打开API看一下
异常类(Throwable),有两个直接的子类,分别是:Error,Exception ;并且它们都实现了一个接口Serializable
接下来,我们分别来看一下这两个子类:
Error:
从API里的描述,我们可以看出,Error一般是比较严重的问题(比如:java内部系统错误,资源耗尽,StackoverflowError等),所以我们一般不对Error进行异常处理,通常要对我们的代码逻辑进行思考改进。
Exception:
Exception的子类可是非常多了,当然,我们没必要去死记硬背这些东西;Exception异常内部又分为了两类,图中我绿色箭头指向的地方:RuntimeException (运行时异常)。
RuntimeException
点进来之后,我们可以看到RuntimeException ,内部还有很多的子类。
非RuntimeException
那么除了运行时异常,剩下的都是编译时异常了,也可以说是编译时异常。
常见的异常
异常类内部都有哪些常见的异常呢?
我们平常写代码中也会出现错误,你比如说数组访问越界,空指针等等
数组访问越界:
空指针异常:
此外还有NullPointerException,ArithmeticException,NumberFormatException..等等,都可以在API上找到,并查看其详细的内容。
异常的处理方式
方式一:try-catch-finally
使用格式:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
....
finally{
//一定会执行的代码
}
我们直接来段代码感受一下:
这段代码中,我们在进行a,b两个变量相除的过程中,由于可能会出现分母为0的情况,所以我们用try-catch-finally语句块对它进行处理。
printStackTrace使用来打印错误信息的,我们可以打开它的源码看一下。
finally的使用
那么我们再来看一下,为什么会打印"-----qiqi------"这句话呢?
很显然,这和finally这个语句块儿有关了,它里面的语句是一定会被执行的
但是finally在我们这个程序里,好像是可有可无的,那我们看去了可不可以呢
很明显,没有问题,那finally语句存在的条件是什么呢?
其实,finally在一些情境下编程时是十分必要的,比如当你打开一个文件想对它操作是,或者你想运行一个数据库的时候,这时候,你用完,无论是成功或是出现异常,我们都希望能关闭掉它,finally里面经常写这样子的语句。
我们再来看一段代码:这是一个类型转换的例子
运行过后我们发现
其实异常之后要报出来的信息,我们是可以自己写成比较人性化的语句的,防止用户使用的时候看到一堆乱码,降低其体验感。
catch语句的顺序
我们把上面的代码中几个catch语句的顺序调整一下,看会出现什么情况。
只有当我们把Exception放到 任意其他两个前面,程序都会报错,这是为什么呢?
首先我们可以发现的是,NullPointerException和NumberFormatException都是Exception的子类,
到这里我们可以思考一下;
Exception的范围肯定比子类的大,当他写在前面的时候,是不是其他的两个子类压根没机会执行了;所以当异常类有子父类关系的时候,我们一定要注意catch语句的顺序。
方式二:throws方法
在编写程序的时候,我们可能并不能确定怎么处理这种异常,此时我们可以用throws将该方法的异常抛出给调用者来进行处理。
这上面这段代码中,我们在divide方法中考虑到分母不能为0的情况,排除了异常Exception,此时你在调用的时候如果不对方法进行处理,我们可以看到,编译的时候就会报错!
我们在处理的时候,仍然要用到try-catch语句块儿来进行处理。
public class ThrowsTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int result = 0;
try {
result = div(4,2);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
public static int div(int x,int y) throws Exception {
int result = x / y;
return result;
}
}
处理之后,如果你在为分母赋值为0的情况下,我们的try-catch就会对异常进行处理。
由此我们可以看出,throws并没有真正的处理掉异常,而只是将异常抛给了他的调用者去处理。
重写方法异常抛出的原则
我们知道,NumberFormatException和NullPointerException是Exception的子类;
原则:当我们写一个异常类的子类去继承父类的时候,子类抛出的异常的范围要是 大于父类抛出的异常范围则会报错!
手动抛出异常
以上的这些异常都是系统在编译和运行时自动识别帮我们抛出的,除了这种由系统自动进行的情况呢,也可根据人工需求手动进行抛出
我们以一个简单的例子来说明这一点:
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
// s.setId(123);
try {
s.setId(-123);
System.out.println(s.toString());
}catch(Exception e){
e.printStackTrace();
}
}
}
class Student{
private int id;
public void setId(int id) throws Exception {
if(id > 0){
this.id = id;
}else{
throw new Exception("输入的数据异常!");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
这段代码中,当我们要设置的学号为负数时,我们需要提醒输入者,输入的数据是有异常的。
这里我们在方法内部将异常类创建出来所使用的关键字是:throw,这里要注意区分throws和throw的区别。
throw是方法内部将异常对象创建出来的;
throws是方法声明时,将异常抛出给调用者的;
如果我们设置的是负数的话,则会对异常进行处理(这里的错误信息是我们调用printStackTrace产生的)。
自定义异常类
了解了异常类之后,那我们如何自定义一个异常类来使用呢?
不会没关系,我们可以打开jdk源码,看看它是怎么定义的,我们照猫画虎。
上来就定义了一大串数字;其实这里是一个序列号,不了解没关系,我们照这样子来就可以;
剩下的还有一些重载的构造器,有无参的,还有一个参数是messasge的 ,下面还有一些其他参数的构造器,我们可以自己打开看看。
异常类是有体系的,就算是我们自定义也要和人家的体系相符,也就是说我们自定义的异常类要继承于现有的异常类,一般情况下我们都不去继承Error类。
自定义异常类:
public class MyException extends Exception{
static final long serialVersionUID = -3387516993124229989L;
private int age;
public MyException(){
super();
}
public MyException(String message ){
super(message);
}
public int getAge(){
return age;
}
}
自定义异常类的测试:
public class MyExceptionTest {
public void myAge(int age) throws MyException{ //写他的父类Exception也可以
if(age >= 0 && age < 200){
System.out.println("您的年龄为:" +age);
}else{
throw new MyException("你输入的年龄非法,请重新输入");
}
}
public static void main(String[] args) {
MyExceptionTest m = new MyExceptionTest();
try {
m.myAge(200);
} catch (MyException e) {
e.printStackTrace();
}
}
}
运行结果:
总的来说,异常这块还是不难的,大家加油努力学习!