异常的理解

异常指的是程序在执行过程中,出现非正常情况,不处理会导致 JVM 非正常停止,并非代码逻辑或语法出现问题,比如,客户端输入的数据格式错误,要读取的文件不存在,网络不畅通等等…

异常抛出的机制

java 把不同异常用不同的类表示,一旦出现异常,就会创建该异常类的对象,并且抛出 throw 程序员就可以捕获 catch 这个异常对象

异常体系

Throwable 异常体系的根父类

常用方法

  • public void printStackTrace():打印异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用 printStackTrace
  • public void getMessage():获取发生异常的信息

Error 错误 与 Exception 异常

Error

  • Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等 严重情况。一般不编写针对性的代码进行处理
  • 例如:StackOverflowError(栈内存溢出)和 OutOfMemoryError(堆内存溢出,简称 OOM)

Exception

  • 其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行、否则一旦发生异常,程序也会挂掉
  • 例如:数组越界,空指针异常…

编译时异常和运行时异常

编译时异常(checked 异常、受检异常):

  • 在编译代码阶段,编译器就给出明确警告,去处理异常代码,否则无法编译生成字节码文件
  • 例如:FileNotFoundException,文件找不到异常

运行时异常(runtime 异常、unchecked 异常、非受检异常):

  • 在编译代码阶段,编译器不做异常检查,只有代码运行起来,发现异常,通常是代码编写不当引起的
  • java.lang.RuntimeException: 类及它的子类都是运行时异常
  • 例如:ArrayIndexOutOfBoundsException,数组越界、ClassCastException类型转换异常

异常的处理方式⭐

Java 采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序 代码分开,使得程序简洁、优雅,并易于维护

try-catch-finally(捕获异常)

try-catch-finally 即、抓抛模型

  1. 过程1(抛):程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出,一旦抛出,此程序就不执行其后的代码

  2. 过程2(抓):针对于过程1中抛出的异常对象,进行捕获处理,此捕获处理的过程,就称为抓、 一旦将异常进行了处理,代码就可以继续执行

注:如果一个异常回到 main()方法,并且 main()也不处理,则程序运行终止

捕获异常语法

try{
  // 可能产生异常的代码
}
catch(异常类型 e){
  // 产生形参类型的异常时的处理
}
catch(异常类型 e){
  // 产生形参类型的异常时的处理
} 
finally{
  // 最后执行的代码,无论有无异常
}

catch 中常用异常处理的方式

  • public void printStackTrace():打印异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用 printStackTrace
  • public void getMessage():获取发生异常的信息

例如

@Test
public void TestException(){
  try{
    String str = "hello";
    str = null;
  }catch(NullPointerException e){
    // 空指针异常 NullPointerException
    e.printStackTrace();
  }catch(ClassCastException e){
    // 类型转换异常 ClassCastException
     e.printStackTrace();
  }finally{
    System.out.printIn("finally!");
  }
  //此处的代码,在异常被处理了以后,是可以正常执行的
	System.out.println("hello")
}

thorws(声明抛出异常)

在编写方法时,可能有些代码会出现异常,在方法体中不适合处理,即可声明抛出异常,让调用方法者负责处理异常

声明抛出异常格式

修饰符 返回值类型 方法名(参数) throws 异常类名1, 异常类名2,...{}

// 例如
public void method throws FileNotFoundException,IOException{
  // 读文件的操作可能产生 FileNotFoundException 
  // 或 IOException 类型的异常
  FileInputStream fis = new FileInputStream(file);
}

@Test
public void TestThrows(){
  // 调用有声明抛出异常的方法
  // 处理
  try{
    method();
  } catch(FileNotFoundException e){
    e.getMessage();
  } catch(IOException e){
    e.printStackTrace();
  }
  
}

总结体会

如果是运行时异常(RuntimeException)没有 try 和 catch 捕获,Java 自己也能捕获,并且编译通过 ( 但运行时 会发生异常使得程序运行终止 ),所以可以不作处理

如果抛出的异常是 IOException 等类型的非运行时异常,则必须捕获处理

开发中,方法 a 中依次调用了方法 b,c,d 等方法,方法 b,c,d 之间是递进关系, 如果方法 b,c,d 中有异常,通常使用 throws,而方法 a 中选择使用 try-catch-finally

自定义与手动抛异常

手动抛出异常 throw

格式:throw new 异常类名(参数)

throw 语句抛出的异常对象,和 JVM 自动创建和抛出的异常对象一样

  • 编译时异常类型的对象,同样需要使用 throws 或者 try…catch 处理,否则编译 不通过
  • 运行时异常类型的对象,编译器不提示
  • 可以抛出的异常必须是 Throwable 或其子类的实例

自定义异常

自定义异常用来:需要根据自己业务的异常情况 来定义异常类。例如年龄负数问题,考试成绩负数问题等等

使用步骤:

  1. 继承一个异常类型:Exception(编译时异常)或 RuntimeException(运行时异常)
  2. 建议提供至少两个构造器,一个是无参构造,一个是 (String message) 构造器
  3. 提供 serialVersionUID

例如

class MyException extends Exception{
  static final long serialVersionUID = 23423423435L;
  private int idNumber;
  
  public MyException(){}
  public MyException(String message, int id){
    super(message);
    this.idNumber = id;
  }
  public int getId(){
    return idNumber;
  }
}

class TestMyException {
  public void regist(int num) throws MyException{
    if(num<0){
      throw new MyException("人数为负数不合理",num);
    } else System.out.println("登记人数" + num);
  }
  
  public static void main(String args[]) {
    TestMyException t = new TestMyException();
    try{
      t.regist(100);
     }catch(MyException e){
      e.getMessage();
    }
 	}
  
}

注意:

  1. 自定义的异常只能 throw 抛出
  2. 自定义异常最重要的是异常类的名字和 message 属性。当异常出现时,可以根据名字 判断异常类型
  3. 自定义异常对象只能手动抛出。抛出后由 try…catch 处理,也可以甩锅 throws 给调用 者处理

总结:类比上游排污,下游治污

image-20230606175644039


参考资料

尚硅谷Java基础