Java中的异常Exception详解(下)

148 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

文章目录

自定义异常

如果要自定义异常类,则扩展 Exception 类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。

按照国际惯例,自定义的异常应该总是包含如下的构造函数:
1.一个无参构造函数
2. 一个带有 String 参数的构造函数,并传递给父类的构造函数。
3. 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数
4. 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数

栗子1:修改航空订票系统

这篇文章是基于 安卓页面和组件练习项目(航空订票系统)项目基础上写的

我们在登录页增加异常处理,新增两个异常类 WrongPasswordException、UserNameNotFoundException 来分别处理密码输入错误和用户名未找到的情况。

WrongPasswordException

public class WrongPasswordException extends Exception {
    public WrongPasswordException() {
        super();
    }

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

    public WrongPasswordException(String message, Throwable cause) {
        super(message, cause);
    }

    public WrongPasswordException(Throwable cause) {
        super(cause);
    }

    protected WrongPasswordException(String message, Throwable cause, boolean
            enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

UsernameNotFoundException

public class UsernameNotFoundException extends Exception{
    public UsernameNotFoundException() {
        super();
    }

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

    public UsernameNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public UsernameNotFoundException(Throwable cause) {
        super(cause);
    }

    protected UsernameNotFoundException(String message, Throwable cause, boolean
            enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

UserService的登录方法改为
如果密码不正确 throw new WrongPasswordException();
如果用户名不存在throw new UsernameNotFoundException();

修改 login 方法如下:

public boolean login(String userName, String password) throws WrongPasswordException, UsernameNotFoundException {
        //调用上面方法获得所有用户信息
        User[] a = findAll();
        /*for (int i = 0; i < a.length; i++) {
            String u = a[i].getUsername();
            String p = a[i].getPassword();
            if (u.equals(userName) && p.equals(password)) {
                return true;
            }
        }
        return false;*/
        for (int i = 0; i < a.length; i++) {
            User u = a[i];
            if (userName.equals(u.getUsername())) {
                if (password.equals(u.getPassword())) {
                    return true;
                } else {
                    throw new WrongPasswordException();
                }
            }
        }
        throw new UsernameNotFoundException();
    }

MainActivity中处理登录的方法改为
调用登录方法时处理异常
修改 doLogin() 方法

private void doLogin() {
        String userName = editText1.getText().toString();
        String password = editText2.getText().toString();
        Log.d(TAG, "用户名:" + userName + "密码:" + password);
        UserService service = new UserService();

        try {
            boolean b = service.login(userName, password);
            startActivity(new Intent(this, MenuActivity.class));
            finish();
        } catch (WrongPasswordException e) {
            Toast.makeText(this, "密码错误", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        } catch (UsernameNotFoundException e) {
            Toast.makeText(this, "用户名不存在", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
      
        /*
          Log.d(TAG, "登录是否成功" + b);
        if (b) {
            startActivity(new Intent(this, MenuActivity.class));
            finish();
        } else {
            Toast.makeText(this, "登录失败请检查用户名和密码", Toast.LENGTH_SHORT).show();
        }*/
    }

快速定位异常

例如我们运行程序,输入不存在的用户名,程序会输出异常信息。虽然警告错误很多,但是需要注意的信息是很少的,只需要两点就可以快速定位错误

1、错误类型
UsernameNotFoundException

2、自己包下文件出错行号
UserService 53行
在这里插入图片描述

异常的注意事项

1、当子类重写父类的带有 throws 声明的函数时,其 throws 声明的异常必须在父类异常的可控范围内——用于处理父类的 throws 方法的异常处理器,必须也适用于子类的这个带 throws 方法 。这是为了支持多态。

例如,父类方法 throws 的是2个异常,子类就不能 throws 3个及以上的异常。父类 throws IOException,子类就必须 throws IOException 或者 IOException 的子类。

2、Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常 会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。

也就是说,Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。

3、在 try 块中即便有 return,break,continue 等改变执行流的语句,finally 也会执行。

4、finally 中的 return 会覆盖 try 或者 catch 中的返回值。

public class Main {
    public static void main(String[] args) {
        int result;

        result = foo();
        System.out.println(result);     /2

        result = bar();
        System.out.println(result);    /2
    }

    @SuppressWarnings("finally")
    public static int foo() {
        try {
            int a = 5 / 0;
        } catch (Exception e) {
            return 1;
        } finally {
            return 2;
        }

    }

    @SuppressWarnings("finally")
    public static int bar() {
        try {
            return 1;
        } finally {
            return 2;
        }
    }
}

5、finally 中的 return 会抑制(消灭)前面 try 或者 catch 块中的异常

public class Main {
    public static void main(String[] args) {
        int result;
        try {
            result = foo();
            System.out.println(result);           //输出100
        } catch (Exception e) {
            System.out.println(e.getMessage());    //没有捕获到异常
        }

        try {
            result = bar();
            System.out.println(result);           //输出100
        } catch (Exception e) {
            System.out.println(e.getMessage());    //没有捕获到异常
        }
    }

    //catch中的异常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } catch (ArithmeticException amExp) {
            throw new Exception("我将被忽略,因为下面的finally中使用了return");
        } finally {
            return 100;
        }
    }

    //try中的异常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } finally {
            return 100;
        }
    }
}

6、finally 中的异常会覆盖(消灭)前面 try 或者 catch 中的异常

public class Main {
    public static void main(String[] args) {
        int result;
        try {
            result = foo();
        } catch (Exception e) {
            System.out.println(e.getMessage());    //输出:我是finally中的Exception
        }
        
        try {
            result = bar();
        } catch (Exception e) {
            System.out.println(e.getMessage());    //输出:我是finally中的Exception
        }
    }

    //catch中的异常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } catch (ArithmeticException amExp) {
            throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
        } finally {
            throw new Exception("我是finally中的Exception");
        }
    }

    //try中的异常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } finally {
            throw new Exception("我是finally中的Exception");
        }

    }
}

上面的4、5、6这 3 个例子都异于常人的编码思维,因此建议:

1、不要在 fianlly 中使用return。
2、不要在 finally 中抛出异常。
3、减轻 finally 的任务,不要在 finally 中做一些其它的事情,finally 块仅仅用来释放资源是最合适的。
4、将尽量将所有的 return 写在函数的最后面,而不是 try … catch … finally 中。

Java中的异常和处理详解