异常

为什么要用都异常

  • Java中的异常(Exception)英文单词是例外的意思(学英语),如果不处理异常,将会导致软件异常中断,崩溃,退出,严重影响用户的使用和体验如果合理的应用异常处理那将会减少软件出现的错误,可以友好的提示用户,提升用户的体验。

异常是什么?

  • 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。如:用户输入非法数据,做除法运算的时候除数为0,打开的文件不存在,网络中断,死递归,死循环导致栈溢出等。

异常机制的概述

  • 异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
    • 程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。
      • 编译错误(Exception)是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
      • 运行时错误(RuntimeException)是因为程序在执行时,运行环境发现了不能执行的操作。
      • 逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。

异常的结构

  • Java中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。

  • Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。

Throwable类中常用方法如下:

1. 返回异常发生时的详细信息
public string getMessage();
2. 返回异常发生时的简要描述
public string toString();

3. 返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,
可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的
信息与getMessage()返回的结果相同
public string getLocalizedMessage();

4. 在控制台上打印Throwable对象封装的异常信息
public void printStackTrace();

异常的解决方法(try-catch)

具体代码如下:

package chapter05;

public class Exception_oop {
public static void main(String[] args) {

// TODO 异常
/*

异常处理语法:

TODO try :尝试
TODO catch : 捕捉(可以连续捕获,由小异常到大异常)
捕捉多个异常的时候,需要先捕捉范围小的异常,然后再捕捉范围大的异常
TODO finally : 最终执行的逻辑

try {
可能会出现异常的代码
如果出现异常,那么JVM会将异常进行封装,形成一个具体的异常类,然后将这个异常抛出
所有的异常对象都可以被抛出
} catch ( 抛出的异常对象 对象引用 ) {
异常的解决方案
} catch () {

} finally {
最终执行的代码逻辑
}
*/
int i = 0;
int j = 0;

try {
j = 10 / i; // 这里的i不能为0
// 设置异常类型
} catch (ArithmeticException e) {
//e.getMessage() // 错误的消息
//e.getCause() // 错误的原因
e.printStackTrace(); // 错误追踪
// 输出: java.lang.ArithmeticException(异常类型): / by zero
i = 10;
j = 10 / i;
} finally {
System.out.println("最终执行的代码");
}
System.out.println(j); // 1
}
}

常见异常:

package chapter05;

public class Exception_oop {
public static void main(String[] args) {

// TODO 异常(运行期异常:均可以通过编程手段解决的异常)
// 1. 除数为0的算术异常:java.lang.ArithmeticException
// 运行期异常
// int i = 0;
// if ( i != 0 ) {
// int j = 10 / i;
// }

// 2 : 空指针异常 : java.lang.NullPointerException
// 调用了一个为空(null)对象的成员属性或成员方法时,就会发生异常
User3 user = null;
// if ( user != null ) {
// System.out.println(user.toString());
// }
try {
//System.out.println(user.toString());
System.out.println(user.name); // 静态方法直接读取属性是可行的,即便实例化的对象为空
} catch (NullPointerException e) {
System.out.println("对象为空,需要分析数据为空的原因");
}

// 3. 索引越界 : ArrayIndexOutOfBoundsException
String[] names = new String[3];
names[0] = "zhangsan";
names[1] = "lisi";
names[2] = "wangwu";
// 编程手段解决索引越界
if ( names.length == 4 ) {
names[3] = "zhaoliu";
}

for ( int ii = 0; ii < names.length; ii++ ) {
System.out.println(names[ii]); // zhangsan lisi wangwu
}

// 4. 字符串索引越界:StringIndexOutOfBoundsException
String s = "abc";
//System.out.println(s.charAt(3)); // 小于长度不会报错
System.out.println(s.substring(4)); // 大于长度才会报错

// TODO 异常
// 5. 格式化异常:NumberFormatException
// String s = "a123";
// Integer i = Integer.parseInt(s);
//
// System.out.println(i);

// 6 类型转换错误:java.lang.ClassCastException
Object obj = new User5();
// 判断 obj 是否为 Emp5 的实例对象
if ( obj instanceof Emp5 ) {
Emp5 emp = (Emp5)obj; // 条件判断成功就将 obj 转化为 Emp5 类型
}
}
}

class User5 {

}
class Emp5 {

}

转换异常(throws关键字)

  • 所谓转换一场顾名思义就是将发生的异常转换在抛出去,下面举一个例子,实际上异常是ArithmeticException,但是我转换成为Exception,也就是我们自己构建异常对象转换异常类型
package chapter05;

public class Exception_oop1 {
public static void main(String[] args) throws Exception {

// TODO 异常转换
User8 user = new User8();

int i = 10;
int j = 0;

// 捕获异常操作
try {
user.test(i, j);
} catch (Exception e) {
e.printStackTrace();
}
}
}

class User8 {
// 如果方法中可能会出现问题,那么需要提前声明,告诉其他人,我的方法可能会出问题。
// 此时需要使用关键字 throws
// 如果程序中需要手动抛出异常对象,那么需要使用throw关键字,然后new出异常对象
public void test( int i, int j ) throws Exception { // throws -> 提示使用者可能会出现异常,异常类型为 Exception
try {
System.out.println( i / j);
} catch (ArithmeticException e) {
// 原本异常是 ArithmeticException 但是下面我抛出一个新的异常 Exception,这就是异常转换
throw new Exception();
}
}
}

自定义异常

  • 自定义异常常用于我们的日常开发中,因为我们使用java中内置的异常通常无法满足我们的业务需求,例如登录的时候,账号和密码不正确,这时就会有问题,这就是一个异常,那如何描述这个异常呢?java中并没有这样的一个具体的类,因此我们就需要自己去定异常,来描述这个场景.
package chapter05;

public class Exception_oop2 {
public static void main(String[] args) throws Exception {
// 调用方法 使用 try-catch捕获异常
try {
login("zhangsan","123");// 首先爆出异常: "账号错误,请重新登录!!"
} catch(AccountException accountException){
System.out.println("账号错误,请重新登录!!");
} catch(PasswordException passwordException){
System.out.println("密码错误,请重新登录!!");
} catch(LoginException loginException){
System.out.println("其他错误,请重新登录!!");
}

}

// 自定义异常
/*
* 如果自定义异常类没有表明要抛出的异常是谁时(LoginException 继承 Exception),就需要在方法
* 这里手动指示要跑出的异常方法后面加上 : throws AccountException, PasswordException / LoginException
* */
public static void login(String account , String password){
if(!"admin".equals(account)){
throw new AccountException("账号错误!");
}
if(!"admin".equals(password)){
throw new PasswordException("密码错误!");
}
}
}

// 自定义异常类

// 账号校验异常
class AccountException extends LoginException{ // 当然也可以继承 RuntimeException
public AccountException(String message){
super(message); // 给父类传递一个错误消息
}
}

// 密码校验异常
class PasswordException extends LoginException{ // 当然也可以继承 RuntimeException
public PasswordException(String message){
super(message); // 给父类传递一个错误消息
}
}

// 登录校验异常 (继承 RuntimeException 运行时异常)
class LoginException extends RuntimeException{
/*
* 当然这里也可以使用 Exception 被继承,但是这样就不明确要跑出那一个异常了,就需要在对应的方法调用时
* 抛出指定异常 看上面的 login() + throws AccountException, PasswordException / LoginException
* */
public LoginException(String message){
super(message); // 给父类传递一个错误消息,RuntimeException 接收一个错误消息
}
}