小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
文章目录
自定义异常
如果要自定义异常类,则扩展 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 中。