背景
异常:在 JAVA 语言中,将程序执行中发生的不正常情况称为“异常”。
Java 程序在执行过程中所发生的异常事件可分为两类:
StackOverflowEroor 栈溢出:
public class Test {
public static void main(String[] args) {
main(args);
}
}
上述代码中,递归调用了 main()方法
,最终抛出了 StackOverflowEroor
错误。
因为,在 JVM 中,栈是有默认深度的,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,就会超过栈的默认深度而导致栈溢出。
OOM 堆溢出(内存溢出):
public class Test {
public static void main(String[] args) {
Integer[] arr = new Integer[1024 * 1024 * 1024]
}
}
上述代码中,new 了一个数组,数组长度是 1G,又因为是 Integer 类型,一个元素占 4 个字节,所以,这个数组占用内存 4GB。最后,造成堆内存空间不足,抛出了 OOM。
对于那些 Error,我们一般不做处理;但对于那些 Exception,就要进行相应的处理了。
对于那些 Exception,一般有两种解决方案:一是一遇见错误就终止程序运行;另一种是处理异常:程序员在编写程序时,就考虑到错误的检测、错误消息的提示、错误的处理。但我们一般选择后者。
异常的分类
异常分为两类:编译时异常(受检异常)、运行时异常 (非受检异常)。
编译时异常是在代码书写时,编译器给你检查提示你要进行 try-catch 或 throws 处理;对于运行时异常,编译器不会帮你自动检查,当你运行程序时,虚拟机才会给你报出错误让你去处理,这个往往是我们编码逻辑或不规范导致的。
异常处理机制
背景
在编写程序时,经常要在可能出现错误的地方加上检测的代码。如:x / y 时,要检测分母为 0、数据为空、输入的不是数字而是字符等。过多的 if-else 分支会导致程序的代码加长、臃肿、可读性差。因此要采用异常处理机制。
异常处理机制:JAVA 采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅、并易于维护
异常处理机制有两种方式:
try-catch-finally
try-catch-finally 结构:
try {
// 可能出现异常的代码
} catch(异常类型1 变量名1) {
// 处理异常
} catch(异常类型2 变量名2) {
// 处理异常
} finally {
// 一定会执行的代码
}
当我们要将一个数值型的字符串转换为整形时,需要对应进行 try-catch。如:
public class ExceptionTest {
public static void main(String[] args) {
String str = "123";
String strTwo = "abc";
int num = 0;
try {
num = Integer.parseInt(strTwo);
} catch (NumberFormatException e) {
System.out.println("数据格式转换异常");
}catch (Exception e) {
System.out.println("抛出异常了");
}
System.out.println(num);
}
}
当给方法 Integer.parseInt(strTwo)
的入参传的值是 “数字型”时,则不会抛出异常;否则,会抛出 NumberFormatException
异常。
说明:
finally 语句
finally
语句的作用:finally 语句里面的代码是一定会被执行的。即使 catch 语句中又出现异常了、try 中有 return 语句、catch 中有 return 语句等。
例1:
public class FinallyTest {
public static void main(String[] args) {
try {
int a = 6;
int b = 0;
int result = a / b;
} catch (ArithmeticException e) {
System.out.println("抛出算术异常");
} catch (Exception e) {
e.printStackTrace();
}
// 依旧会被执行
System.out.println("try-catch 结构之外的语句被执行");
}
}
当执行上述代码后,屏幕打印:
抛出算术异常
try-catch 结构之外的语句被执行
try-catch 结构之外的语句被执行
打印出这句话表明:抛出异常后,依旧会执行 try-catch 语句之外的代码。
但我们现在在 catch 语句中加一些代码,使之也抛出异常,即:
public class FinallyTest {
public static void main(String[] args) {
try {
int a = 6;
int b = 0;
int result = a / b;
} catch (ArithmeticException e) {
System.out.println("抛出算术异常");
// 会抛出数组下标越界异常
int[] arr = new int[10];
System.out.println(arr[10]);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("try-catch 结构之外的语句被执行");
}
}
执行上述代码后,发现屏幕打印出:
抛出算术异常
没有打印之前的那一行,说明:没有执行执行那一行代码.
给它加上 finally 语句试试,即:
public class FinallyTest {
public static void main(String[] args) {
try {
int a = 6;
int b = 0;
int result = a / b;
} catch (ArithmeticException e) {
System.out.println("抛出算术异常");
int[] arr = new int[10];
System.out.println(arr[10]);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally 语句被执行");
}
System.out.println("try-catch 结构之外的语句被执行");
}
}
执行代码后,发现屏幕打印出:
抛出算术异常
finally 语句被执行
例2:
大家可以猜猜 FinallyTest#method()
方法的结果是多少
public class FinallyTest {
public static void main(String[] args) {
FinallyTest finallyTest = new FinallyTest();
int method = finallyTest.method();
// 2
System.out.println(method);
}
}
public int method() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 2;
}
}
执行上述代码后,发现屏幕打印出:“2”。
加上 finally 语句后,那执行结果又是多少呢?
public int method() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 2;
} finally {
System.out.println("我一定会执行");
return 3;
}
}
执行上述代码后,发现输出结果是:“3”。
通过这两个例子来体会到 finally 语句的作用了吧。
那么,finally 语句一般用于哪些场景呢?
资源释放。如:数据库连接、输入输出流、网络编程 Socket 等。例如:
代码如下:
public class FileTest {
public static void main(String[] args) {
File file = new File("test.txt");
boolean exists = file.exists();
if (!exists) {
try {
boolean newFile = file.createNewFile();
if (!newFile) {
System.out.println("创建文件失败,文件名为" + file.getName());
return;
}
} catch (IOException e) {
System.out.println("创建文件失败,文件名为" + file.getName());
return;
}
}
FileOutputStream out = null;
try {
out = new FileOutputStream(file);
String message = "我爱中国";
// 写入内容
out.write(message.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) {
try {
// 关闭流
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
throws + 异常类型
如:
public class ThrowTest {
public static void main(String[] args) {
Student student = new Student();
try {
student.regist(-1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
class Student {
private int id;
// 手动抛出异常
public void regist(int id) throws Exception{
if (id > 0) {
this.id = id;
} else {
throw new Exception("输入的是非法数据");
}
}
}
如何解读这句话:子类重写的方法抛出的编译时异常类型不大于父类被重写的方法抛出的异常类型(多态的使用)
给出如下例子:
public class Father {
public void arth() throws ClassNotFoundException {
}
public static void main(String[] args) {
Father father = new Son();
try {
father.arth();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
public class Son extends Father{
@Override
public void arth() throws ClassNotFoundException{
}
}
对于上述代码,如果去掉这个限制 “子类重写的方法抛出的编译时异常类型不大于父类被重写的方法抛出的异常类型”,则修改 Son#arth()
方法成如下:
public class Son extends Father{
@Override
public void arth() throws Exception{
}
}
那么,在 Father#main()
方法中调用 arth()
方法时,必定要进行 try-catch,那么 catch 中应该捕获什么异常对象呢?父类中的方法是没有 throws Exception 的啊。所以,这是不成立的,并且是必须要加那条限制的。
好了,下一期咱们来分享“如何自定义异常吧”~~
因篇幅问题不能全部显示,请点此查看更多更全内容