前段时间考完OCA决定对java异常做一个总结,但一直被其他的知识点给占用了时间,于是一直托到今天。本篇博客继续从java的基础知识出发,希望能帮助大家。
示例的源码可以直接通过csdn下载,也可以通过git导出:https://github.com/igdnss/java_exception.git
异常的概念
其实程序中的异常跟我们生活中的异常一样,例如生活中遇到家里灯坏了,灯坏了怎么办,要么修,要么换一个,不可能一直放那不管,不用灯,而是想办法去解决,要让生活继续下去。程序中的异常指的是程序中的不正常现象。出现的异常需要对其处理,如果不处理,程序将终止运行,造成不必要的后果。
异常的分类
所有的一切异常和错误都继承于Throwable这个类,位于java.lang包中。
- Error:JVM,硬件,执行逻辑错误,无法通过手动处理。
例如:StackOverflowError, OutOfMemoryError - Exception: 程序在运行和配置中产生的问题,可以通过手动处理
例如:
RuntimeException:运行时异常,可处理可不处理。
CheckedException:检查(编译)时异常,必须处理,不处理程序编译不通过。
常见的异常
异常 | 说明 | 类别 |
---|---|---|
NullPointerException | 空指针异常 | 运行时异常 |
ArrayIndexOutOfBoundException | 数组越界异常 | 运行时异常 |
ClassCastException | 类型转换异常 | 运行时异常 |
ArithmeticException | 算术异常 | 运行时异常 |
NumberFormatException | 数字格式化异常 | 运行时异常 |
FileNotFoundException | 文件不存在异常 | 检查(编译)时异常 |
示例:
public class ExceptionTest {
public static void main(String[] args) {
//空指针异常
String address= null;
System.out.println(address);
//数组越界异常
String[] arr = {"a"};
System.out.println(arr[2]);
//算术异常
System.out.println(1/0);
//类型转换异常
Object str = "aaa";
Long i = (Long)str;
//数字格式化异常
int number = Integer.parseInt("1a");
//文件不存在异常
File file = new File("c:\\abc.txt");
FileInputStream inputStream = new FileInputStream(file);
}
}
异常的产生
程序在运行时遇到不符合规范的代码或结果时,会产生异常,程序员通过throw关键字可以将异常抛出,使用程序能正常运行下去。如果出现异常时不处理,在哪出现,程序在哪中断,不会往下执行。
异常的传递
异常如果不处理会一直往上传递,如果最终没有处理异常,jvm会进行默认异常处理(打印异常信息)。
示例1:传递异常未处理
public class ExceptionTransfer1 {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
divided(a, b);
}
public static void divided(int a, int b) {
System.out.println(a/b);
}
}
运行结果:
示例2:传递异常已处理
public class ExceptionTransfer1 {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
try {
divided(a, b);
} catch (Exception e) {
System.out.println("除数不能为0");
}
}
public static void divided(int a, int b) {
System.out.println(a/b);
}
}
运行结果:除数不能为0
异常的处理
通过5个关键字处理异常:
- try: 执行可能产生的异常代码
- catch: 捕获异常,并处理
- finally: 无论是否发生异常,代码总能执行(除了强制退出)
- throw: 手动抛出异常
- throws: 声明方法可能要抛出的各种异常,注意与throw的区别。throw是抛出,throws是声明。
try catch
使用try将可能出现异常的代码包起来,当真的有异常时,交给catch处理。
示例:指定正确的异常
public class TryCatchTest {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
try {//函数中涉及到除法,因此可能会有算术异常,所以这里加上try
divided(a, b);
} catch (Exception e) {// 这里可以写成Exception(父类),也可以写指定的异常ArithmeticException,异常处理成功会后续代码会继续执行,因此会打印Hello
System.out.println("除数不能为0");
}
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
运行结果:
除数不能为0
Hello
注:catch中处理异常时如果指定特定异常,必须要指对,如果指定错误,异常是不会被处理的。
示例:指定错误的异常
public class TryCatchTest2 {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
try {//函数中涉及到除法,因此可能会有算术异常,所以这里加上try
divided(a, b);
} catch (FormatterClosedException e) {// 这里随便指定了一个异常,但不是算术异常,所以程序会中断,不会打印hello
System.out.println("除数不能为0");
}
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.research.exception.TryCatchTest2.divided(TryCatchTest2.java:22)
at com.research.exception.TryCatchTest2.calculate(TryCatchTest2.java:14)
at com.research.exception.TryCatchTest2.main(TryCatchTest2.java:9)
为了防止这种情况,建议catch时直接Exception
try catch finally
前文中已提到finally块中不管异常有没有发生,都会被执行,一般用于释放资源,例如关闭文件,断开连接。但程序强制退出时,finally块不会被执行。
示例:
public class TryCatchFinallyTest {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
try {//函数中涉及到除法,因此可能会有算术异常,所以这里加上try
divided(a, b);
// System.exit(0);当执行这句时,Haha不会打印
} catch (Exception e) {
System.out.println("除数不能为0");
}finally {
//必执行
System.out.println("Haha");
}
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
多重catch
发生异常时按顺序逐个匹配直到结束,如果匹配成功,后续的catch不再执行,这里需要注意写码时子类异常在前,父类异常在后,否则编译不通过。
示例:
public class MultiCatchTest {
public static void main(String[] args) {
calculate(1, 0);
}
public static void calculate(int a, int b) {
try {//函数中涉及到除法,因此可能会有算术异常,所以这里加上try
divided(a, b);
Integer i = Integer.parseInt("124");
}catch(NumberFormatException e) {
System.out.println("必须要为全数字");
}catch (Exception e) {
System.out.println("除数不能为0");
}finally {
//必执行
System.out.println("Haha");
}
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
try finally
没有catch,所以此组合不能捕获异常,但可以将异常向上抛出,只是在异常发生时做释放资源的操作,后续程序不会被执行。
示例:
public class TryFinallyTest {
public static void main(String[] args) {
try {
calculate(1, 0);
}catch(Exception e) {
System.out.println("在这里处理异常");
}
}
public static void calculate(int a, int b) {
try {//函数中涉及到除法,因此可能会有算术异常,所以这里加上try
divided(a, b);
} finally {
//释放资源
System.out.println("我想要怒放的生命");
}
//有异常时,此句不会执行到
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
声明异常(throws)
使用throws关键字声明一个方法可能抛出的异常,调用该方法的地方需要对声明的异常进行处理,如果不处理,可能会出问题。声明的异常如果是编译期异常则必须要处理,如果不处理,编译不通过。如果声明的是运行时异常,如果不处理,一旦发生,则程序中断。简单的说就是告诉调用者,调用这个方法可能抛出异常,麻烦处理一下,如果不处理后果自负。这里处理异常时可以使用try catch,也可以在调用者上继续声明此异常,让其向上声明。注意与throw的区别,throw是抛出某一具体的异常,一次只能抛出一个,throws可以声明多个。
示例1:声明运行时异常
public class ThrowsTest1 {
public static void main(String[] args) {
//这里不处理不会出编译错误,因为是运行时异常
try {
calculate(1, 0);
}catch(ArithmeticException e) {
System.out.println("处理异常");
}
}
public static void calculate(int a, int b) throws ArithmeticException {
divided(a, b);
//divided产生异常时,此名不执行
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
示例2:声明编译时异常
public class ThrowsTest2 {
public static void main(String[] args) {
//必须处理,不处理编译不通过
try {
calculate(1, 0);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void calculate(int a, int b) throws FileNotFoundException {
divided(a, b);
// divided产生异常时,此名不执行
System.out.println("Hello");
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
抛出异常(throw)
运行时异常都是系统自动抛出,但有些问题需要程序员自行抛出异常。自行抛出异常时使用throw + 异常对象。如果抛出的时运行时异常,方法调用处理,可不处理。如果抛出的是编译时异常,抛出的地方需要对其进行捕获或者通过方法向上声明,方法调用者必须处理,或者继续向上声明。
示例:
public class ThrowTest {
public static void main(String[] args) {
try {
calculate(1, 0);
}catch(RuntimeException e) {
e.printStackTrace();
}
}
public static void calculate(int a, int b) {
if (b != 0) {
divided(a, b);
} else {
throw new RuntimeException("除数不能为0");
}
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
自定义异常
jdk给我们提供了很多异常,但有时这些异常使用时不能见名知意,例如上文中的RuntimeException("除数不能为0")
,只知道是个运行时异常,通过message方可知道他的用处,其实这里可以自己定义一个异常。自定义异常里需要继承Exception或其子类,一般自定的异常都是RuntimeException,所以常常继承RuntimeException.自定义时必须要提供构造方法,一个无参的一个有参的。定义好后没必要再写其它的代码,其意义就在于异常的名字。
**示例:重构ThrowTest **
public class ThrowTest {
public static void main(String[] args) {
try {
calculate(1, 0);
}catch(DenominatorCanNotBe0Exception e) {
e.printStackTrace();
}
}
public static void calculate(int a, int b) {
if (b != 0) {
divided(a, b);
} else {
throw new DenominatorCanNotBe0Exception("除数不能为0");
}
}
public static void divided(int a, int b) {
System.out.println(a / b);
}
}
方法重写
这里的方法重写指的是带有异常声明的方法重写,需要注意以下三点。
- 方法名、参数列表、返回值类型必须和父类相同
- 子类的访问修饰符和父类相同或比父类更宽
- 子类中的方法,不能抛出比父类更多、更宽的检查时异常
结束语
至此,关于java异常的知识点就介绍完了。希望本文能帮助大家,祝大家在IT之路上少走弯路,一路绿灯不堵车,测试一性通过,bug秒解!