Skip to Content

Java异常

异常指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。

异常体系

Error: 严重错误Error,Java虚拟机无法解决的错误,只能事先避免。常见的错误有虚拟机错误(VirtualMachineError)、内存溢出(OutOfMemoryError)、线程死锁(ThreadDeath)。

Exception: 表示异常,因为编程错误或偶然的外在因素导致的一般问题,可以视同针对性的代码进行处理。分为非检查异常(UncheckedException),检查异常(CheckedException),非检查异常允许忽略,检查异常必须捕捉或者声明抛出。

而运行时异常指的是java.lang.RuntimeException类以及它的子类。编译时异常指的是Exception类的子类除去运行时异常的其他异常。

异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Errorjava.lang.Exception ,平常所说的异常指java.lang.Exception

常见的运行时异常

NullPointerException 空指针异常:当应用程序试图在需要对象的地方使用null时,会抛出该异常。

比如调用一个null引用的变量进行调用方法:

Object obj = null; System.out.println(obj.hashCode());

ArithmeticException 数学运算异常:当出现异常的运算条件时,会抛出此异常。

比如一个整数除以0时:

int i = 10 / 0;

ArrayIndexOutOfBoundsException 数组下标越界异常:用非法所有访问数组时跑出的异常。如果索引为负数或者大于等于数组的大小,则会抛出次异常。

比如一个数组只有三个元素,索引应为0,1,2,但访问索引为3的元素时:

int[] arr = new int[3]; arr[3] = 10;

ClassCastException 类型转换异常:当试图将对象强制转换为不是实例的子类时,跑出该异常。

比如A类是B类的父类,也是C类的父类,但是B类和C类没有继承关系,这时将B类的对象转成C类时:

public class ExceptionTest { public static void main(String[] args) { A a = new B(); C c = (C) a; } } class A {} class B extends A {} class C extends A {}

异常处理

Java异常处理的五个关键字:try、catch、finally、throw、throws

捕获异常 try-catch-finally

try-catch的方式就是捕获异常,Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

try:该代码块中编写可能产生异常的代码。 catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。 finally:有一些特定的代码无论异常是否发生,都需要执行。

语法格式:

try{ // 捕获异常 // 编写可能会出现异常的代码 }catch(异常类型 异常变量名){ // 当发生异常时,系统将异常封装到形参的对象中,传递给catch块 // 然后根据自己的需求处理捕获到的异常 // 记录日志/打印异常信息/继续抛出异常等等 }finally{ // 无论是否有异常,都执行 }

① try块后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。try、catch和finally都不能单独使用,必须连用。

这样的写法如果出现了异常,结果是和没有捕获异常的情况是一样的,程序会直接崩掉

try { System.out.println("可以没有catch块"); } finally { System.out.println("如果没有catch块,必须要有finally块"); }
try { System.out.println("也可以没有finally块"); } catch (ClassCastException e) { // 可以有多个catch块 System.out.println("第一个catch块"); } catch (NullPointerException e) { System.out.println("第二个catch块"); }

② 如果有多个catch子句,要求父类异常在后,子类异常在前。并且如果遇到异常,多个catch块只会匹配最近的一个。

因为异常会引发程序跳转,导致有些语句执行不到,所以

③ 在finally代码块中存放的代码都是一定会被执行的。

例如,在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),都得在使用完之后,最终关闭打开的资源,一般这些代码放在finally代码块中。

④ 如果finally有return语句,永远返回finally中的结果,避免该情况。

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。终止JVM的方法:System.exit(非0的数);

执行下面代码可以看到,结果为3:

public class ExceptionTest { public static void main(String[] args) { System.out.println(getI()); } public static int getI(){ try { return 1; } catch (Exception e) { return 2; } finally { return 3; } } }

声明异常 throws

关键字throws运用于方法声明之上 ,表示当前方法不处理异常,提醒该方法的调用者来处理异常(抛出异常)。若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

一般用于一个方法中可能出现某个异常,但是不能确定如何处理这个异常,则这个方法可以声明抛出异常。

语法格式:

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

当子类重写父类抛出异常的方法时,声明的异常必须是父类方法所声明异常的同类或子类。

public static void main(String[] args) throws IOException { read("a.txt"); } public static void read(String path) throws FileNotFoundException, IOException { if (!path.equals("a.txt")) { // 假设如果不是 a.txt 则认为该文件不存在,抛出异常 throw new FileNotFoundException("文件不存在"); } if (!path.equals("b.txt")){ throw new IOException(); } }

① 如果没有捕获异常,一直声明异常,最终会被JVM处理,JVM的处理方式就是输出异常信息和退出程序。

如果发现异常,输出异常信息,并且退出程序

② 对于编译时异常,程序中必须处理,可以使用try-catch或者throws

如果不处理的话会报错。

可以将其声明在方法上:

也可以使用try-catch包围进行解决:

③ 对于运行时异常,程序中如果没有处理,则默认会声明throws XXXException

④ 如果父类抛出了多个异常,子类重写父类方法时,可以抛出和父类相同的异常、父类异常的子类、不抛出异常。

下面B类、C类、D类都是可以通过编译的:

class A { public void m1() throws IOException, SQLException {} } class B extends A { @Override public void m1() throws IOException {} } class C extends A { @Override public void m1() {} } class D extends A { // FileNotFoundException是IOException的子类 @Override public void m1() throws FileNotFoundException {} }

⑤ 父类方法没有抛出异常,子类重写父类该方法时不可抛出异常,只能捕获处理,不能声明抛出。

这样就不能通过编译

抛出异常 throw

throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。throw抛出的只能够是可抛出类Throwable或者其子类的实例对象。

语法格式:

// 自己抛出,调用者处理 返回值 方法名(参数) throws 异常类名{ throw new 异常类名(参数); }
// 自己抛出,自己处理 try{ // 代码块1 throw new 异常类名(参数); } catch (异常类名 变量名){ // 代码块2 }

如果产生了问题,throw 就会将异常进行抛出,也就是将问题返回给调用者。调用者可以进行捕获处理,或者使用 throws 声明处理。

public static void main(String[] args) { //创建一个数组 int[] arr = {2,4,52,2}; //根据索引找对应的元素 int element = getElement(arr, 4); System.out.println(element); } /* * 根据 索引找到数组中对应的元素 */ public static int getElement(int[] arr, int index){ //判断 索引是否越界 if(index < 0 || index > arr.length‐1){ /* 判断条件如果满足,执行完throw抛出异常对象后,方法无法继续运算, 就会结束当前方法的执行,并将异常告知给调用者。 */ throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~") } int element = arr[index]; return element; }

自定义异常类

使用Java内置的异常类可以描述在编程时出现的大部分异常情况,也可以通过自定义异常描述特定业务产生的异常类型。

自定义异常类:定义一个类,去继承Throwable类或者它的子类。

定义编译期异常: 继承于java.lang.Exception定义运行期的异常: 继承于java.lang.RuntimeException

// 业务逻辑异常 public class RegisterException extends RuntimeException{ /** * 空参构造 */ public RegisterException() { } /** * 有参构造 * @param message 表示异常提示 */ public RegisterException(String message) { super(message); } }

throw和throws的区别

描述使用位置后面跟的代码
throw抛出异常,手动生成一个异常对象的关键字方法体中异常对象
throws异常处理的一种方式方法声明处异常类型