Java--异常处理

掌握Java中的异常处理机制,并学会定义异常


异常定义

在Java语言中,将程序执行中发生的不正常情况都可以称为异常。异常发生在程序运行期间,它影响了正常的程序执行流程。比如:用户输入不符合要求,程序打开文件时文件不存在等。

Java异常机制的作用:可以使程序异常处理代码和正常业务代码分离,提高程序健壮性。
示例一个简单的算数异常:

public class Demo01 {
    public static void main(String[] args) {
      int a = 10;
      int b = 0;
      System.out.println(a/b);
    }
}

由于除数不能为0,程序出现异常,控制台输出异常信息:

image

异常在Java中以类和对象存在,根据异常信息,可以发现有一个类名java.lang.ArithmeticException,点开看源码可知它是一个类,有有参构造方法和无参构造方法,继承RuntimeException,代表所有的算数异常类。根据异常信息“at com.lic.exception.Demo01.main(Demo01.java:8)”可知在源码第8行发生了这个异常事件。实际上代码在执行到第8行,在底层调用了ArithmeticException类里面的构造方法创建了对象,将这个异常对象抛出。

image

这个信息会被我们看到,我们就会对其有相应的异常措施,会根据异常信息的描述对程序进行改进,提高程序的健壮性,改进如下:

public class Demo01 {
    public static void main(String[] args) {
      int a = 10;
      int b = 0;
      if(b==0){
          System.out.println("输入错误,除数不能为0");
      }else{
            System.out.println(a/b);
        }
    }
}

改进后的代码增添了if判断语句,提高了程序的健壮性

继承结构及分类

Java中异常以类和对象的形式存在,自然有自己的继承结构。程序在运行过程中出现的问题无论是异常还是错误都是可抛出的,Error和Exception的父类都是Throwable。

Error表示错误,在Java中只要发生错误,是无法处理的,最终只有一个结果就是JVM停止执行,程序退出。例如:Java虚拟机运行错误,当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,JVM一般会选择线程终止。

Exception表示异常,异常是可处理的,有两个处理结果,要么程序终止运行,要么处理完异常向下继续执行。

Exception继续向下分两大类:

  1. 受控异常(检查性异常):Exception类的直接子类就是受控异常。比如用户错误或问题引起的异常,是程序员无法预见的,例如打开一个不存在的文件时,一个异常就发生了,所有的受控异常要求在程序编写阶段预处理,如果不处理,编译器就会报错。
  2. 非受控异常(运行时异常):RuntimeException类的直接子类就是非受控异常。非受控异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

自定义异常

异常类要么继承Exception类,要么继承RuntimeException类,然后提供两个构造方法,一个无参,一个带有String参数的。

接下来自定义一个异常类:非法名称异常(IllegalNameException)。比如用户在注册的时候,要求用户名长度必须在[6-14]位之间,其他长度当作异常处理。自定义异常如下:

public class IllegalNameException extends RuntimeException{
    public IllegalNameException() {
    }
    public IllegalNameException(String str) {
        super(str);
    }
}

手动抛出异常

异常的发生首先需要new异常对象,然后使用throw关键字抛出异常对象,异常就发生了,异常发生后,之后的代码就终止了。
假设用户在注册的时候,要求用户名长度必须在[6-14]位之间,就让异常发生。
先编写自定义异常类,再编写一个处理用户相关业务的UserService类,提供注册方法。

/**
 * 处理用户相关业务
 */
public class UserService {
    /**
     * 用户注册
     * @param username
     */
    public void register(String username){
        if(username==null||username.length()<6||username.length()>14){
            //满足以上条件代表异常发生
            //抛出异常对象,让异常发生
            throw new IllegalNameException("非法用户名");
            /*
            这一句代码不使用异常处理机制,这样写:
            System.out.println("非法用户名");
            return;
            return一旦执行,程序不继续往下执行了;
            异常处理机制的代码既中断了程序的继续执行,又将异常信息传递给了调用者
             */
            //异常发生处之后的代码不会执行
        }
        //异常未发生
            System.out.println(username+"注册成功");
    }
}

编写测试程序调用UserService类的register方法完成测试。

public class UserClient {
    public static void main(String[] args) {
        //输入用户名
        String username = "Jack";
        //用户注册
        new UserService().register(username);
    }

运行结果如下:

image

程序运行到UserService.java的第14行出现异常,然后UserService的register方法执行结束,并将这个异常对象抛出给调用者“UserClient.java的第8行”,所以异常信息中描述在UserClient.java的第8行发生了IllegalNameException这个异常。

假设用户名合法,如下:

public class UserClient {
    public static void main(String[] args) {
        //输入用户名
        String username = "Jackson";
        //用户注册
        new UserService().register(username);
    }

结果如下:
image

异常处理

受控异常要求在程序编写阶段预处理,如果不处理,编译器就会报错,处理方法有两种:

  1. 在方法声明位置使用throws关键字进行声明以便抛出
  2. 使用try catch进行异常的捕捉
    对之前自定义的异常类进行修改,继承Exception修改之后其他程序出现问题
public class IllegalNameException extends Exception{
    public IllegalNameException() {
    }
    public IllegalNameException(String str) {
        super(str);
    }
}

出现错误:
image

错误是未处理异常,使用try catch解决:

public class UserService {

    public void register(String username){
        if(username==null||username.length()<6||username.length()>14){
            try{
                throw new IllegalNameException("非法用户名");
            }catch (IllegalNameException e){
                e.printStackTrace();
            }
        }
        //异常未发生
            System.out.println(username+"注册成功");

    }
}

通过上面代码可以看出,自己采用throw new IllegalNameException(“非法用户名”)让异常发生,又用try catch捕获,没有意义,自己手动抛出异常是希望调用者处理异常,所以应采用throws,声明以便抛出的方式。

代码如下:

public class UserService {
    public void register(String username) throws IllegalNameException{
        if(username==null||username.length()<6||username.length()>14){
                throw new IllegalNameException("非法用户名");
        }
            System.out.println(username+"注册成功");
    }
}

但是方法使用throws声明后其他程序出现错误

image

出现语法错误,因为register方法声明位置上有throws IllegalNameException的代码,所以编译器知道register方法会可能发生IllegalNameException,调用者就知晓并能够处理。怎样解决未处理异常呢?使用try catch捕获还是采用throws,声明以便抛出呢?
假设使用throws,代码如下:

public class UserClient {
    public static void main(String[] args) throws Exception{
        //输入用户名
        String username = "Jackson";
        //用户注册
        new UserService().register(username);
    }
}

语法合法,但是这样做不好,如果出现异常,采用throws处理异常,将此异常抛给调用者处理,JVM调用main方法,JVM会终止执行,所有程序全部结束。这显然不是我想要的,我希望处理完异常,程序继续执行,提高程序的健壮性。所以使用try catch捕获。

public class UserClient {
    public static void main(String[] args) {
        //输入用户名
        String username = "Jack";
        //用户注册

        try {
            new UserService().register(username);
        } catch (IllegalNameException e) {
            System.out.println("用户名不合法");
        }
    }
}

运行结果如下:
image

try catch

try表示尝试执行大括号里面的代码,如果某一行代码出现异常,发生异常之后的代码不执行进入catch语块中执行,catch表示捕获异常,可以编写多个,进入哪个catch取决于catch小括号的异常类型,并且try语句块发生异常后创建的异常对象的内存地址会赋给catch后面的e 变量。也就是e引用了刚发生的异常对象。
catch分支如果有多个,只能有一个分支执行,那么catch语句写多个的时候,有自上而下的顺序匹配的。
案例:定义两个异常类AException,BException,再编写一个测试类,里面有一个接收整数的方法。
写两个自定义异常类:

public class AException extends Exception {
    public AException() {
    }

    public AException(String message) {
        super(message);
    }
}
public class BException extends Exception {
    public BException() {
    }

    public BException(String message) {
        super(message);
    }
}

写有test方法的类MathService:

public class MathService {
    public void test(int i)throws AException,BException{
        if(i==1){
            throw new AException("a类异常");
        }else if(i==2){
            throw new BException("b类异常");
        }
        System.out.println("没有异常");
    }
}

测试类MathClient:

public class MathClient {
    public static void main(String[] args) {
        int i = 2;
        try {
            new MathService().test(i);
            System.out.println("如果前面代码发生异常,此代码不会执行");
        } catch (AException e) {
            System.out.println("A类异常");
        } catch (BException e) {
            System.out.println("B类异常");
        }
        System.out.println("如果前面代码发生异常,此代码会执行!!!try catch后");
    }

运行结果:
image

finally语句块

finally语句块必须和try语句联合使用,不能独立使用,放在finally里面的语句必须执行的。
例如:

public class Demo01 {
    public static void main(String[] args) {
      int a = 10;
      int b = 0;
        try {
            System.out.println(a/b);
        } catch (Exception e) {
            System.out.println("除数不能为0");
        }
        finally {
            System.out.println("finally语句块");
        }
    }
}

结果如下:
image
假设在try语句块写了return语句,那么finally语句块中的程序还会执行。

public class Demo01 {
    public static void main(String[] args) {
        try {
            System.out.println("try");
            return;
        }
        finally {
            System.out.println("finally语句块");
        }
    }
}

结果如下:
image
try语句中虽然有return语句,但是finally语句块还是会执行,return语句一旦执行,main方法就结束了,所以finally语句块的执行实在return之前。
但是,下面这个例子结果不是finally语句块先执行。

public class Demo02 {
    public static void main(String[] args) {
        System.out.println(test());
    }
    public static int test(){
        int i = 10;
        try {
            return i;
        } finally {
            i +=100;
        }
    }
}

结果是10而不是110image
由于此程序比较特殊,它实际是将i变量中存储的值又重新存入到一个新的临时变量,return返回的值是新的临时变量的值,代码等同于:

public class Demo02 {
    public static void main(String[] args) {
        System.out.println(test());
    }
    public static int test(){
        int i = 10;
        try {
            int j = 10;
            return j;
        } finally {
            i +=100;
        }
    }
}

所以结果是10不是110。
接下来再测试把return语句换成System.exit(0);

public class Demo01 {
    public static void main(String[] args) {
        try {
            System.out.println("try");
            System.exit(0);
        }
        finally {
            System.out.println("finally语句块");
        }
    }
}

结果如下:
image
发现finally语句块没有执行,如果是采用System.exit(0)的方式退出JVM,那么finally语句块不会执行。

方法覆盖与异常

方法覆盖之后不能抛出比父类方法更宽泛的异常,可以一样或变小。
测试:
编写UserService:

public class UserService {
    public void register(String username) throws IllegalNameException{
        if(username==null||username.length()<6||username.length()>14){
                throw new IllegalNameException("非法用户名");
        }
            System.out.println(username+"注册成功");
    }
}

编写SubUserService继承UserService并重写register方法:

public class SubUserService extends UserService{
    @Override
    public void register(String username) throws IllegalNameException {
        super.register(username);
    }
}

编译通过,说明语法正确,重写之后的方法抛出的异常与父类一样,如果重写后不抛出异常,编译也通过,说明重写后可以更少。

public class SubUserService extends UserService{
    @Override
    public void register(String username)  {
           }
}

如果重写后更多呢,抛出Exception异常
就错误,由于父类没有抛出Exception异常,子类重写也不能抛出。
image
记住一条规则:当子类覆盖父类方法时,子类重写的方法不能比父类方法抛出更宽泛的异常,可以一样或者更少。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值