目录
一、异常的概念与体系
1.1. 异常的概念
在Java中,将程序执行过程中发生的不正常行为称为异常。异常是Java语法中的核心环节,是一种针对程序的错误处理的科学方式。
在C语言,是没有异常的机制的,因为C语言比较“古老”了;在C++中,引入了异常,但只是一个“半成品”;而Java的异常是比较完善的体系。
这里我们要注意异常与编译出错的差别。还有几种常见的异常,如数组的越界访问,空指针异常。
System.out.printl("hello");//编译出错
System.out.println(10 / 0);//异常,运行时出错,会抛出异常
int[] array1 = {1,2,3};
System.out.println(array1[10]);//数组的越界访问
int[] array2 = null;
System.out.println(array2.length);//空指针
1.2. 异常的体系结构
从上图中我们可以看到,Throwable是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception。Error是在JVM内部使用的, 程序员一般用不到Error。我们需要重点学习的是Exception。这么多类我们不需要背,因为Java标准库提供了很多现成的异常。
1.3. 异常的分类
(1)受查异常,在程序编译期间发生的异常,这样的异常必须要显式处理。
public class Person {
private String name;
private String gender;
int age;
@Override
protected Person clone() {
return(Person) super.clone();
}
}
public class Main {
public static void main(String[] args) {
Person one = new Person();
System.out.println();
}
}
上面的代码会在出现异常的位置下出现红色的波浪线,来提示我们出现了异常。受查异常不处理,代码就无法通过编译。
import java.io.FileNotFoundException;
public class Main {
//throws后面可以接可能抛出的异常
private static void openConfig(String filename) throws FileNotFoundException{
if(!filename.equals("config.txt")){
throw new FileNotFoundException("文件不存在");
}
}
public static void main(String[] args) throws FileNotFoundException {
openConfig("xxx");
}
}
(2) 非受查异常
在程序执行期间发生的异常,RunTimeException以及其子类对应的异常,都称为非受查异常。
二、异常的处理
在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。
2.1. 防御式编程
(1) LBYL:Look Before You Leap。在操作之前就做充分的检查。即:事前防御型。缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
(2)EAFP: It's Easier to Ask Forgiveness than Permission。“事后获取原谅比事前获取许可更容易"。也就是先操作,遇到问题再处理。即:事后认错型
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码 异常处理的核心思想就是 EAFP。
2.2. 异常的抛出
在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者。在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。
语法规则:
throw new xxxException(“产生异常的原因”)
//在数组中查找一个元素
public class Main {
public static int getElment(int[] array,int index){
if(array == null){
// 数组为空, 抛出空指针异常
throw new NullPointerException("传递的数组为null");
}
if(index<0 || index>= array.length){
// 数组下标越界, 抛出另一个异常
throw new ArrayIndexOutOfBoundsException("数组越界异常");
}
return array[index];
}
public static void main(String[] args) {
int[] arrs = {1,2,3};
getElment(arrs,3);
}
}
这样我们就能看到抛出的异常并给了一个提示,正是我们在异常当中所传递的参数。
注意:throw必须写在方法体内部;抛出的对象必须是Exception 或者 Exception 的子类对象; 异常一旦抛出,其后的代码就不会执行
2.3. 异常的捕获
(1)throws关键字
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛 给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。
语法规则:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
import java.io.FileNotFoundException;
public class Main {
//throws加在方法后面,声明这个方法可能抛出的异常
private static void openConfig(String filename) throws FileNotFoundException{
if(!filename.equals("config.txt")){
throw new FileNotFoundException("文件不存在");
}
}
public static void main(String[] args) throws FileNotFoundException {
openConfig("xxx");
}
}
注意:声明的异常必须是 Exception 或者 Exception 的子类;方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可;调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出。
throw与throws的区别:throw是真得抛出来一个异常对象,而throws只是声明了一下,真正抛没抛,不知道。
(2)try-catch捕获并处理
throws只是声明,声明告诉方法的调用者来处理异常。真正进行异常处理,要通过try-catch。
语法规则:
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
import java.io.FileNotFoundException;
import java.io.IOException;
public class Main {
public static void openConfig(String filename) throws IOException{
if(!filename.equals("config.txt")){
throw new FileNotFoundException("文件不存在");
}
}
public static void main(String[] args) {
try {
openConfig("xxx");
System.out.println("调用完成");
} catch (IOException e) {
//e是IOException的一个对象,这个对象是由throw抛出的
System.out.println("进入到异常处理逻辑中");
}
}
}
我们在调用openConfig后面加上一条语句,这是因为openConfig发现了异常,意味着这个代码块不会再执行后面了,直接进入后面的catch语句。
这里还有一个小技巧,我们把鼠标光标放在被调用的方法上,然后alt加回车,直接出现要抛出的异常,很快就能生成。
catch 语句中, 一般会打印出异常的详细信息,printStackTrace() 方法可以打印异常的详细信息,主要就是调用栈。
调用栈就会抛出异常的位置是怎样一层一层调过去的。
catch语句可以有多个具体catch哪些异常,取决于try抛出哪些异常。
import java.io.FileNotFoundException;
import java.io.IOException;
public class Main {
public static void openConfig(String filename) throws IOException {
if(!filename.equals("config.txt")){
throw new FileNotFoundException("文件不存在");
}
}
public static int getElment(int[] array,int index){
if(array == null){
// 数组为空, 抛出空指针异常
throw new NullPointerException("传递的数组为null");
}
if(index<0 || index>= array.length){
// 数组下标越界, 抛出另一个异常
throw new ArrayIndexOutOfBoundsException("数组越界异常");
}
return array[index];
}
public static void main(String[] args) {
try {
String str = null;
str.length();
openConfig("xxx");
System.out.println("调用完成");
} catch (IOException e) {
System.out.println("进入到异常处理逻辑中");
e.printStackTrace();
} catch (NullPointerException e){
System.out.println("空指针异常");
e.printStackTrace();
}
}
}
catch里面每一个独立的代码块,里面的e都是代码块里面的局部变量,所以不用担心e的命名不同。