异常
异常指的是在程序执行过程中,出现非正确的情况,导致虚拟机非正常停止
java文档中是这样描述的
当程序违反Java编程语言的语义约束时,Java虚拟机会将此错误作为异常信号发送给程序。
Java编程语言规定,当违反语义约束时,将抛出异常,并将导致从异常发生点到程序员可以指定的点的非本地控制转移
异常由类Throwable(Object的直接子类)或其子类之一的实例表示。
异常的体系
-
Throwable
: 所有异常的根类java文档中描述:Throwable及其所有子类统称为异常类。类Exception和Error是Throwable的直接子类
Throwable中常用的方法:
-
public void printStackTrace()
:打印异常的详细信息。(堆栈信息)包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
-
public String getMessage()
:获取发生异常的原因。提示给用户的时候,就提示错误原因。
-
public String toString()
:获取异常的类型和异常描述信息(不用)。
-
-
Error
:系统级别错误,严重问题,无法通过处理的错误(例如:内存溢出)Error是所有异常的超类,普通程序通常不会从中恢复
-
Exception
:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行 (例如:数组下标越界、空指针异常等)异常是普通程序可能希望从中恢复的所有异常的超类。
-
RunTimeException
:运行时异常,在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)可以不被处理(捕获或者抛出,在编译时不会报错)
类RuntimeException是Exception的直接子类。RuntimeException是所有异常的超类,这些异常可能在表达式求值过程中因多种原因引发,但仍然可以从中恢复。RuntimeException及其所有子类统称为运行时异常类
-
编译时异常:在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常,在编译器中不处理会报错)
受检异常和不受检异常
-
unchecked exception
:不受检异常,包括运行时异常和其所有子类还有错误类,此类异常并不要求强制进行处理The unchecked exception classes are the run-time exception classes and the error classes.
未检查的异常类是运行时异常类和错误类。
-
checked exception
:除了不受检异常的其他异常,对这类代码要求在代码中进行显式处理选中的异常类是除未选中的异常类别之外的所有异常类别。也就是说,检查的异常类是Throwable及其所有子类,而不是RuntimeException及其子类和Error及其子类。
如何处理异常
-
使用
try、catch、finally
来捕获和处理异常try中包含可能会出现异常的代码
catch中用于捕获并处理指定类型的异常
finally块中的代码无论是否发生异常都会执行,
-
使用
throw、throws
来抛出异常throw用于手动抛出异常对象
throws关键字用于在方法声明中指定可能抛出的异常类型,表示该方法可能会抛出的异常,由调用者来处理
异常处理机制
对于程序中可能发生的错误或异常进行预先处理
捕获异常
-
使用
try-catch
处理格式: try { 可能出现异常的代码 } catch (异常的类型 变量名) { 处理异常 }
例子:
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo { public static void main(String[] args){ try { test(); System.out.println("这行代码会执行吗?"); } catch (ArithmeticException e) { System.out.println(e.getMessage()); e.printStackTrace(); } System.out.println("end..."); } public static void test() throws ArithmeticException{ int a = 1; int b = 0; new Exception(); System.out.println(a/b); } } //输出结果为 / by zero java.lang.ArithmeticException: / by zero at com.kfm.day0830.ExceptionDemo.test(ExceptionDemo.java:25) at com.kfm.day0830.ExceptionDemo.main(ExceptionDemo.java:11) end...
可能出现的情况:
- 如果try语句块中的所有语句正常执行完毕,没有发生异常,那么catch块中的语句不会被执行
- 如果try中的语句发生了异常,并且这个异常与catch块中声明的异常类型匹配,那么try块中剩下的代码将会被忽略,而相应的catch语句块将会被执行。匹配是指catch所处理的异常类型与try块所生成的异常类型完全一致或者是它的父类
- 如果try语句块在执行过程中发生异常,而抛出的异常在catch中的没有被声明,那么程序立即终止运行,程序被迫退出
- catch语句块中可以加入用户自定义处理信息,也可以调用异常对象的方法输出异常信息
-
使用
try-catch-finally
无论try中的语句是否发生异常,finally块中的代码都会执行
例子:
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo1 { public static void main(String[] args) { try { System.out.println(1/0); } catch (Exception exception) { exception.printStackTrace(); //System.exit(0); } finally { System.out.println("除非虚拟机退出不然总会执行"); } } } 输出为: java.lang.ArithmeticException: / by zero at com.kfm.day0830.ExceptionDemo1.main(ExceptionDemo1.java:10) 除非虚拟机退出不然总会执行 如果执行了 System.exit(0); 那么输出结果为 java.lang.ArithmeticException: / by zero at com.kfm.day0830.ExceptionDemo1.main(ExceptionDemo1.java:10)
可能出现的情况:
- 如果try块中的语句正常执行完毕,程序不会进入catch语句执行,但是finally语句块也会被执行
- 如果try块中的语句在执行过程中发生异常,程序进入到catch中捕获异常,catch块中执行完毕后,finally块中也会被执行
- try-catch-finally中,try块是必须存在的,但是catch和finally语句块为可选,但两者至少要出现其一;
- 可以在catch中使用System.exit(0),退出虚拟机,此时finally中的代码不会被执行
即使在catch语句块中存在return语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执
行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语
句退出。例子:
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo2 { public static void main(String[] args) { System.out.println(test()); } public static int test(){ try { System.out.println(1/0); } catch (Exception exception) { System.out.println(exception.getMessage()); return -1; } finally { return 0; } } } 输出结果为 / by zero 0 是因为就算catch中有return语句,但是finally中的语句还是会被执行,但是finally中有return,所以此时方法结束,所以返回0
-
try-catch-catch...-finally
多重catch处理异常,当代码中可能引发多种类型的异常时,可以在一个try块后面跟多个catch块,用来处理不同类型的异常,但是,catch块的排列属性顺序必须是从子类到父类,最后一般是个Exceotion类,如果父类的catch块写在前面,那么子类的将永远不会被执行。一旦系统执行了与异常类型匹配的catch语句块并执行其中的一条catch语句后,其后的catch语句将会被忽略
例子:
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo2 { public static void main(String[] args) { System.out.println(test()); } public static int test(){ try { String str = null; System.out.println(str.charAt(1)); } catch (NullPointerException exception) { System.out.println(exception.getMessage()); } catch(Exception exception){ System.out.println(exception.getMessage()); }finally { return 0; } } }
抛出异常
-
使用
throws
声明抛出异常如果在一个方法体内抛出异常,并希望调用者能够及时地捕获异常,java中通过关键字throws声明某个方法可能抛出的各种异常,以通知调用者。throws可以同时声明多个异常,异常之间使用 ,隔开
将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
package com.kfm.day0830; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo3 { public static void main(String[] args) { try { test(); } catch (ParseException e) { System.out.println("test方法抛出的异常在这里被处理"); e.printStackTrace(); } } public static void test() throws ParseException { String str ="071096 4:5 PM,PDT"; SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); Date parse = simpleDateFormat.parse(str); System.out.println(parse); } } //输出结果为 test方法抛出的异常在这里被处理 java.text.ParseException: Unparseable date: "071096 4:5 PM,PDT" at java.base/java.text.DateFormat.parse(DateFormat.java:399) at com.kfm.day0830.ExceptionDemo3.test(ExceptionDemo3.java:25) at com.kfm.day0830.ExceptionDemo3.main(ExceptionDemo3.java:15) 因为parse方法会报编译时异常,所以需要将异常捕获在方法中进行处理,或者抛出让调用者进行处理
-
使用
throw
抛出异常在编译过程中,有些问题时系统无法自动发现并解决的,如年龄不合法等,此时需要程序员来而不是系统来自行抛出异常
例子:
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo3 { public static void main(String[] args) { try { test(-1); } catch (Exception e) { System.out.println("test方法抛出的异常在这里被处理"); e.printStackTrace(); } } public static void test(int age) throws Exception { if(age < 0){ throw new Exception("年龄不合法"); } } } //输出结果为 test方法抛出的异常在这里被处理 java.lang.Exception: 年龄不合法 at com.kfm.day0830.ExceptionDemo3.test(ExceptionDemo3.java:20) at com.kfm.day0830.ExceptionDemo3.main(ExceptionDemo3.java:11) 因为Exception是检查异常(编译时异常),所以要使用throws抛出异常,给方法调用者处理
-
如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句要么处于 try 块里,显式捕获该异常,要么放
-
在一个带 throws 声明抛出的方法中,即把该异常交给该方法的调用者处理;
-
如果 throw 语句抛出的异常是 Runtime 异常,则该语句无须放在 try 块里,也无须放在带 throws 声明抛出
的方法中;程序既可以显式使用 try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给
该方法调用者处理 -
自行抛出Runtime 异常比自行抛出Checked 异常的灵活性更好。同样,抛出 Checked 异常则可以让编译器提醒
程序员必须处理该异常 -
运行时异常是自动往外抛出的,不需要我们手工抛出。
package com.kfm.day0830; /** * @author by FZB * @date 2023/8/30 */ public class ExceptionDemo3 { public static void main(String[] args) { try { test(-1); } catch (Exception e) { System.out.println("test方法抛出的异常在这里被处理"); e.printStackTrace(); } } public static void test(int age){ if(age < 0){ throw new RuntimeException("年龄不合法"); } } } //输出结果为 test方法抛出的异常在这里被处理 java.lang.RuntimeException: 年龄不合法 at com.kfm.day0830.ExceptionDemo3.test(ExceptionDemo3.java:20) at com.kfm.day0830.ExceptionDemo3.main(ExceptionDemo3.java:11) 因为手动抛出的时运行时异常,所以可以不用放在throws声明抛出的方法中,运行时异常会自动抛出
-
throw和throws的区别
- 作用不同,throw用于自行产生异常并抛出异常,throws用于声明该方法内抛出了异常,如果throw抛出的是一个检查异常(编译时异常)那么必须使用throws将异常抛出,否则编译不通过,而不检查异常会自动抛出
- 使用位置不同:throw位于方法体内部,可以作为单独的语句使用,throws必须跟在方法参数列表的后面,不能单独使用。
- 内容不同:throw只能抛出一个异常对象,throws后面跟异常类,可以跟多个
构造方法可以抛出异常吗
-
构造方法可以抛出异常
如果在构造方法中发生了异常,那么创建对象的过程将被中断,对象将无法成功创建。
package com.kfm.day0830;
/**
* @author by FZB
* @date 2023/8/30
*/
public class ExceptionDemo4 {
int age;
public ExceptionDemo4() throws Exception {
if(age < 0){
throw new Exception("年龄不合法");
}
}
}
-
对子类有什么影响
如果父类构造抛出了编译时异常,那么子类的构造方法也必须抛出异常
构造方法抛出异常对子类的影响与对普通方法的影响类似。如果在父类的构造方法中抛出了异常,子类在创建对象时也会受到影响。子类的构造方法会首先调用父类的构造方法,如果父类的构造方法抛出了异常,那么子类的构造方法也无法正常执行。
package com.kfm.day0830;
/**
* @author by FZB
* @date 2023/8/30
*/
public class ExceptionDemo4 {
int age;
public ExceptionDemo4() throws Exception {
if(age < 0){
throw new Exception("年龄不合法");
}
}
}
class son extends ExceptionDemo4{
public son() throws Exception {
}
}
-
在构造方法中抛出异常和不抛出有什么区别
构造方法抛出异常和不抛出异常的区别在于对象的创建是否成功以及对象的状态。如果构造方法不抛出异常,那么对象将会被成功创建并初始化。但如果构造方法抛出异常,对象将无法被正确创建,可能会处于一个不完整或者不稳定的状态。这可能会导致对象的使用出现问题。
需要注意的是,良好的编程实践建议在构造方法中尽量避免抛出异常,以确保对象的正确创建和初始化。如果有必要在构造方法中处理异常,可以考虑使用适当的错误处理机制,例如在构造方法内部进行异常捕获和处理。
自定义异常
- 为什么要自定义异常
当jdk中的异常类型不满足我们的需求时,可以自定义异常
我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。,例如年龄负数问题,考试成绩负数问题。
-
如何自定义异常
1.定义异常类,并继承Exception或者RuntimeException
2.编写异常类的构造方法,向父类构造方法传入异常描述信息,并继承父类的其他实现方法
3.实例化自定以异常对象,并在程序中使用throw抛出
例子:
package com.kfm.day0830;
/**
* 分数不合法异常
*
* 1.继承Exception类或者RunTimeException
* @author by FZB
* @date 2023/8/30
*/
public class ScoreViolationException extends Exception{
public ScoreViolationException(String message) {
super(message);
}
}
class TestS{
public static void main(String[] args) {
try {
scanner(-1);
} catch (ScoreViolationException e) {
System.out.println(e.getMessage());
} finally {
}
}
public static void scanner(int score) throws ScoreViolationException {
if(score < 0){
throw new ScoreViolationException("分数不合法!");
}
}
}
异常链
在Java中,异常链(Exception Chaining)是指一个异常对象包含对另一个异常对象的引用,从而将多个异常连接在一起,形成一个异常链。这种异常链的概念通常用于记录异常的发生和传递过程,以便更好地理解异常的产生原因。
例子:
package com.kfm.day0830;
/**
* 异常链
*
* @author by FZB
* @date 2023/8/30
*/
public class ExceptionDemo5 {
public static void main(String[] args) {
try {
test3();
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static void test1() throws Exception {
throw new Exception("方法一的异常");
}
public static void test2() throws Exception {
try {
test1();
} catch (Exception exception) {
throw new Exception("方法二的异常", exception);
}
}
public static void test3() throws Exception {
try {
test2();
} catch (Exception exception) {
throw new Exception("方法三的异常", exception);
}
}
}
//输出结果为
java.lang.Exception: 方法三的异常
at com.kfm.day0830.ExceptionDemo5.test3(ExceptionDemo5.java:36)
at com.kfm.day0830.ExceptionDemo5.main(ExceptionDemo5.java:13)
Caused by: java.lang.Exception: 方法二的异常
at com.kfm.day0830.ExceptionDemo5.test2(ExceptionDemo5.java:27)
at com.kfm.day0830.ExceptionDemo5.test3(ExceptionDemo5.java:34)
... 1 more
Caused by: java.lang.Exception: 方法一的异常
at com.kfm.day0830.ExceptionDemo5.test1(ExceptionDemo5.java:20)
at com.kfm.day0830.ExceptionDemo5.test2(ExceptionDemo5.java:25)
... 2 more
-
在要抛出的对象中使用 initCause() 方法,添加上一个产生异常的信息; getCause() 可以获取当前异常对象的上一个异常对象
上部分实现和下面一样,都是传一个Throwable
package com.kfm.day0830;
/**
* 异常链
* 在要抛出的对象中使用 initCause() 方法,添加上一个产生异常的信息; getCause() 可以获取当前异常对象
* 的上一个异常对象
* @author by FZB
* @date 2023/8/30
*/
public class ExceptionDemo6 {
public static void main(String[] args) {
try {
test2();
} catch (Exception exception) {
System.out.println(exception.getCause());
exception.printStackTrace();
}
}
public static void test1() throws Exception {
throw new Exception("方法一的异常");
}
public static void test2() throws Exception {
try {
test1();
} catch (Exception exception) {
Exception e2 = new Exception("方法二的异常");
e2.initCause(exception);
throw e2;
} finally {
}
}
}
//输出为
java.lang.Exception: 方法一的异常
java.lang.Exception: 方法二的异常
at com.kfm.day0830.ExceptionDemo6.test2(ExceptionDemo6.java:29)
at com.kfm.day0830.ExceptionDemo6.main(ExceptionDemo6.java:14)
Caused by: java.lang.Exception: 方法一的异常
at com.kfm.day0830.ExceptionDemo6.test1(ExceptionDemo6.java:22)
at com.kfm.day0830.ExceptionDemo6.test2(ExceptionDemo6.java:27)
... 1 more
可以抛出一个Error吗
可以在Java中抛出 Error
。Error
是 Throwable
类的一个子类,它表示严重的系统错误,通常是无法恢复的问题,例如内存溢出、线程死锁等。与普通的异常(Exception
)不同,Error
通常不应该被捕获和处理,而是应该由系统或者应用程序的顶层进行处理。
当发生 Error
时,通常意味着程序出现了严重的问题,无法继续正常执行。因此,通常情况下,不建议在代码中捕获和处理 Error
package com.kfm.day0830;
/**
* @author by FZB
* @date 2023/8/30
*/
public class ErrorExample {
public static void main(String[] args) {
try {
throwOutOfMemoryError();
} catch (OutOfMemoryError e) {
System.out.println("Caught: " + e);
e.printStackTrace();
}
}
static void throwOutOfMemoryError() {
throw new OutOfMemoryError("Custom OutOfMemoryError");
}
}
输出为:
Caught: java.lang.OutOfMemoryError: Custom OutOfMemoryError
java.lang.OutOfMemoryError: Custom OutOfMemoryError
at com.kfm.day0830.ErrorExample.throwOutOfMemoryError(ErrorExample.java:18)
at com.kfm.day0830.ErrorExample.main(ErrorExample.java:10)
需要强调的是,通常不建议这样做,因为 Error
表示严重的系统问题,应由系统来处理,而不是由应用程序代码来捕获和处理。
总之,抛出 Error
是为了表示无法恢复的系统问题,而不是为了在代码中进行正常的异常处理。