在Java中,将程序执行过程中出现的不正常行为称为异常。比如:
//算术异常
System.out.println(10/0);
//输出:Exception in thread "main" java.lang.ArithmeticException: / by zero
//数组越界异常
int[] arr = {1,2,3};
System.out.println(arr[10]);
//输出:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
//空指针异常
int[] array = null;
System.out.println(array.length);
//输出:Exception in thread "main" java.lang.NullPointerException
异常在Java中以类的形式出现,每一个异常类都可以创建异常对象。异常种类繁多,为了对不同异常或者错误进行很好的分类管理,Java内部维护了一个异常的体系结构:
注:
- throwable是异常体系的顶层类,其派生出两个重要子类error类和exception类;
- error类:指JVM无法解决的严重问题,如:JVM的内部错误、资源耗尽等;
- exception类:指异常产生后程序员可以通过代码进行处理,使程序继续执行的问题;
可以将异常分为:
注:
- 异常分为编译时异常和运行时异常,但所有异常都是在运行阶段发生的,因为,只有运行阶段才可以new对象,异常的发生就是new异常对象。
- 编译时异常一般发生的概率比较高,需要在运行之前进行预处理;运行时异常一般发生的概率比较低,在运行之前不必进行预处理。
public static void main(String[] args) {
System.out.println(10/0);
//这里的hello world 没有执行
System.out.println("hello world");
}
流程:
boolean ret = false;
ret = 登录游戏();
if (!ret){
处理登录游戏异常;
return;
}
ret = 开始匹配();
if (!ret){
处理开始匹配异常;
return;
}
ret = 游戏确认();
if (!ret){
处理游戏确认异常;
return;
}
ret = 载入游戏画面();
if (!ret){
处理载入游戏画面异常;
return;
}
.....
缺点:正常代码和错误代码流程放在一起,代码整体显的比较混乱。
try {
登陆游戏();
开始匹配();
游戏确认();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
......
优势:正常流程和错误流程是分离开的,程序员更关注正常流程,代码更清晰,更容易理解。
所以,我们在处理异常时的核心思想就是 EAFP,我们需要明白“异常一旦出现是必须要被处理了才能继续执行后面的代码的”,处理异常可以交给JVM处理:直接中止程序执行;交给用户自己处理:可以按照自己的意愿来进行合适的处理,再执行后面的代码;
在Java中,异常处理主要有5个关键字:throw、throws、try、catch、finally
在编写程序时,如果程序中出现错误,此时就需要把错误的信息告诉调用者;
在Java中,可以通过throw关键字,来声明一个指定的异常对象,将错误的信息告诉调用者。但是它不会处理异常,必须搭配throws或者try-catch来使用。
用throw声明异常后会自动抛出
由方法的调用者使用throws配合使用:上报给上一级,直到交给JVM处理程序直接中止,不会执行后面的代码;
由方法的调用者使用try-catch配合使用:用户自己处理,处理之后可以继续执行后面的代码;
public static int getElement(int[] arr,int index){
if (arr == null){
//通过throw 告诉调用者出现的异常情况信息
throw new NullPointerException("传递的数组为空");
}
if (index < 0 || index >= arr.length){
//通过throw 告诉调用者出现的异常情况信息
throw new ArrayIndexOutOfBoundsException("要访问的数组下标越界");
}
return arr[index];
}
//实现一个获取数组指定位置元素的方法
public static void main(String[] args) {
int[] arr = {1,2,3};
int ret = getElement(arr,2);
System.out.println(ret);
}
注:
- throw必须写在方法体的内部;
- 抛出的对象必须是Exception或者Exception的子类对象;
- 如果抛出编译时异常,用户必须处理,否则无法通过编译;
- 如果抛出运行时异常,用户可以不处理,交给JVM处理;
- 异常一旦抛出,其后的代码就不会执行;
异常的捕获也就是异常的具体处理方式,有两种:异常声明throws和try-catch异常捕获;
当方法中抛出编译时异常(使用throws抛出异常后必须有人来进行处理),用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者处理。即当前方法不处理异常,提醒方法的调用者处理异常,如果方法的调用者也不处理,则会交给JVM处理,程序直接中止运行。
public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
return arr[index];
}
public static void main(String[] args) throws ArrayIndexOutOfBoundsException{
int[] arr = {1,2,3};
int ret = getElement(arr,3);
System.out.println("执行后面的代码");
}
//会打印出异常日志 但不会打印出 执行后面的代码
public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
return arr[index];
}
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
int ret = getElement(arr,3);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("执行后面的代码");
}
//会打印出异常日志 和 执行后面的代码
注:
public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
return arr[index];
}
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
int ret = getElement(arr,3);
//下面的代码不会执行
System.out.println("getElement之后的语句");
System.out.println("getElement之后的语句");
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
//这里的代码还会执行
System.out.println("执行后面的代码");
}
//会打印出异常日志 和 执行后面的代码
throws并没有对异常进行真正的处理,而是将异常抛出给方法的调用者,由调用者处理;如果真正要对异常进行处理,就需要try-catch。
public static int getElement(int[] arr,int index) throws ArrayIndexOutOfBoundsException{
return arr[index];
}
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
int ret = getElement(arr,3);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("执行后面的代码");
}
注:
- catch()里的 e ,表示是e引用,保存的地址是那个new出来的异常对象的地址;
- try中可能会抛出多个异常,则建议使用多个catch来一对一精确捕获;
- catch中捕获的异常类型既可以是具体的异常类型,也可以是该异常类型的父类型。
- catch中捕获多个异常时,从上到下,必须遵守异常的范围从小到大。否则先捕获到该类的父类型时,就不会继续向下检测,不会捕获到具体的异常类型了。
ArrayIndexOutOfBoundsException,NullPointerException{ return arr[index]; } public static void main(String[] args) { int[] arr = {1,2,3}; try { int ret = getElement(arr,1); }catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); }catch (NullPointerException e){ e.printStackTrace(); }catch (Exception e){ e.printStackTrace(); } } ``` 5. catch捕获多个异常时,异常间可以用 | 分割 ```java public static int getElement(int[] arr, int index) throws ArrayIndexOutOfBoundsException,NullPointerException{ return arr[index]; } public static void main(String[] args) { int[] arr = {1,2,3}; try { int ret = getElement(arr,1); }catch (ArrayIndexOutOfBoundsException | NullPointerException | Exception){ System.out.println("出现异常"); } } ```
在某些特定的代码中,我们必须要求其释放持有的资源,比如文件读取等,但是当遇到异常时,它下面的一些代码就不会被执行到,这样就会引发一些问题。因此,我们使用finall来解决这个问题。
finall子句必须和try一起出现,不能单独编写。
即使try语句块中的代码出现了异常,finall中的代码也是一定会执行的。
public static void main(String[] args) {
try {
System.out.println("try");
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally");
}
}
注:
- try和finally联用,没有catch;
try { System.out.println("try"); return; } finally { System.out.println("finally"); } } ``` 2. 如果try语句块中有return,那么try、finally、return的执行顺序是怎样的? 先执行try、再执行finally、最后执行return;finally执行的时机是在return以前。 3. 如何让finally失效呢? System.exit() ``` public static void main(String[] args) { try { System.out.println("try"); //退出JVM 退出之后finally中的语句就不执行了 System.exit(0); } finally { System.out.println("finally"); } } ``` 4. 一般在finally中进行一些资源清理的工作。
Java中,虽然已经内置了丰富的异常类,但是并不能完全表示在实际开发中遇到的所有异常,此时就需要我们自己定义异常。
自定义异常有两步:
下面以登录为例,演示自定义异常类的使用。
//自定义用户名错误类
class UsernameFalseException extends Exception{
public UsernameFalseException(){
}
public UsernameFalseException(String msg){
super(msg);
System.out.println(msg);
}
}
//自定义密码错误类
class PasswordFalseException extends Exception{
public PasswordFalseException(){
}
public PasswordFalseException(String msg){
super(msg);
System.out.println(msg);
}
}
public class Test {
private static String USER_NAME = "admin";
private static String PASS_WORD = "123456";
public static void login(String username,String password) throws UsernameFalseException, PasswordFalseException {
if (!username.equals(USER_NAME)){
throw new UsernameFalseException("用户名错误");
}
if (!password.equals(PASS_WORD)){
throw new PasswordFalseException("密码错误");
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String username = scanner.nextLine();
String password = scanner.nextLine();
try {
login(username,password);
System.out.println("登录成功");
}catch (UsernameFalseException e){
e.printStackTrace();
}catch (PasswordFalseException e){
e.printStackTrace();
}
}
}