一、什么是异常
程序运行中出现的各种问题就称为异常
二、异常处理机制
JAVA语言中针对异常而设计的处理机制称为异常处理机制
三、 异常的分类
1. JAVA中所有的异常都是派生于Throwable类
2. Error类和Exception类派生于Throwable类,是异常的最主要的两大类
3. Error类描述程序运行时系统的内部错误等,该异常非常严重,出现后只能通告用户,然后终止程序,但是很少出现
4. Exception类又派生出了两大类(主要),RuntimeException和IOException
5. RuntimeException是描述程序错误导致的异常,而IOException是程序本身没有问题,但是由于I/O等错误产生的异常
6. RuntimeException的异常一定是程序编写者的问题,代表着这样的问题是人为的,可以避免的
7. 所有派生于Error类和RuntimeException类的异常我们成为非受查异常,一个是没办法解决,一个是可以代码中控制避免的
8. 派生于IOException等其他类的异常统称为受查异常,代表着是不可控制但可以解决的,因此要进行检查
四、声明受查异常
1. 当我们定义一个方法时,若方法中的代码有可能产生受查异常,那么我们必须要声明该异常或者解决该异常,否则编译报错
2. 声明异常:在方法签名后+关键字throws+异常类型
public void test(String name) throws FileNotFoundException
{
File file=new File(name);
FileInputStream inputStream=new FileInputStream(file); //将可能产生FileNotFoundException异常,
}
3. 当多个异常存在时,声明异常则异常类型间用,号隔开
public void test(String name) throws FileNotFoundException, ClassNotFoundException
{
File file=new File(name);
FileInputStream inputStream=new FileInputStream(file); //将可能产生FileNotFoundException异常,
Class class1=Class.forName(name); //反射获取类,可能产生ClassNotFoundException异常
}
五、抛出受查异常
1. 当方法中代码逻辑明确有错误产生的时候,我们可以抛出一个异常用于结束方法,提示调用者进行异常处理
2. 抛出异常的方式; 找到合适的异常类,生成异常类对象,通过throw关键字抛出
3. 抛出异常的同时必须声明该异常,否则编译报错
4. 抛出异常后调用该方法的方法必须要解决异常或者继续抛出异常
public class Test2
{
public void readFile(String name) throws FileNotFoundException
{
File file=new File(name);
if(!file.exists())
{
FileNotFoundException exception=new FileNotFoundException("文件不存在");
throw exception;
}
System.out.println("文件存在");
}
}
六、处理异常
1. java异常处理机制使用try catch 语句处理
2. 可能产生异常的代码放入到try代码块中,若产生异常,则剩余代码不执行,跳入到catch代码块中进行处理解决
3. catch中的条件参数就是异常的类型,必须是try代码中可能产生的异常类型,否则编译报错,设置catch毫无意义
如下:若没有异常发生,则代码执行顺序为1,2,3,5;若发生异常,则顺序为1,2,4,5
public class Test
{
public void readFile(String name)
{
try
{
File file=new File(name); //1
FileInputStream inputStream=new FileInputStream(file); //2 将可能产生异常的代码放入到try代码块中
System.out.println("文件流产生,文件存在"); //3
}
catch (FileNotFoundException e) //设置异常类型及变量,必须是try代码块中可能产生的异常类型,否则毫无意义
{
System.out.println(e.getMessage()); // 4 打印异常的信息
}
System.out.println("方法结束"); //5
}
}
4. 若有多个异常存在,可以使用多个catch语句进行异常捕获处理
public class Test
{
public void readFile(String name)
{
try
{
File file = new File(name);
FileInputStream inputStream = new FileInputStream(file); // 将可能产生FileNotFoundException异常
System.out.println("文件流产生,文件存在");
Class class1 = Class.forName(name); // 可能产生ClassNotFoundException异常
System.out.println("类存在");
}
catch (FileNotFoundException e) // 设置异常类型及变量,必须是try代码块中可能产生的异常类型,否则毫无意义
{
System.out.println(e.getMessage());
}
catch (ClassNotFoundException e) // 设置异常类型及变量,必须是try代码块中可能产生的异常类型,否则毫无意义
{
System.out.println(e.getMessage());
}
System.out.println("方法结束");
}
}
5. 若有多个异常存在,也可以使用一个catch语句进行多个异常捕获处理(这种方式要求异常之间不存在子类关系)
正确设置:
public class Test
{
public void readFile(String name)
{
try
{
File file = new File(name);
FileInputStream inputStream = new FileInputStream(file); // 将可能产生FileNotFoundException异常
System.out.println("文件流产生,文件存在");
Class class1 = Class.forName(name); // 可能产生ClassNotFoundException异常
System.out.println("类存在");
}
catch (FileNotFoundException | ClassNotFoundException e) // 设置异常类型及变量,必须是try代码块中可能产生的异常类型,否则毫无意义
{
System.out.println(e.getMessage());
}
System.out.println("方法结束");
}
}
错误设置:
public class Test
{
public void readFile(String name)
{
try
{
File file = new File(name);
FileInputStream in = new FileInputStream(file); //可能产生FileNotFoundException异常
System.out.println("文件流产生,文件存在");
int b;
while((b=in.read())!=-1) //可能产生IOException异常
{
System.out.println("读取文件");
}
}
catch (FileNotFoundException | IOException e) // 报错,因为FileNotFoundException 是IOException 的子类,不能这样写,只能多个catch处理
{
System.out.println(e.getMessage());
}
System.out.println("方法结束");
}
}
七、finally语句
1. finally语句用于异常处理机制中,代表着无论是否发生异常,最终都要执行finally语句
2. 如关闭文件资源,in.close(),若这方法在try语句中,一旦读取发生异常,程序会跳过剩余代码,因此不会执行该方法,若在catch语句中,那么不发生异常的话也不会执行该方法,因此finally语句就有发挥的地方
3. 特别注意点:try代码块,catch代码块,finally代码块内的变量不互通,不能相互调用,因此一般变量要放在try代码块前
八、示例代码
1. 该代码是之前个人编码常用的思路,捕捉异常关闭资源都照顾到了,看起来完美
2. 但存在一个问题,当文件不存在时,第一个catch语句处理FileNotFoundException异常,而后进入finally语句,此时in是null对象,in.close就会报java.lang.NullPointerException异常,而这个异常是我们没有考虑到的
public class Test
{
public void readFile(String name) throws IOException
{
File file = new File(name);
FileInputStream in = null; // 变量定义在try之前,这样finally才能调用
try
{
in = new FileInputStream(file); // 可能产生FileNotFoundException异常
System.out.println("文件流产生,文件存在");
int b;
while ((b = in.read()) != -1) // 可能产生IOException异常
{
System.out.println("读取文件中");
}
}
catch (FileNotFoundException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.println(e.getMessage());
}
finally
{
in.close();
System.out.println("不管异常,最终都要执行关闭资源方法");
}
System.out.println("方法结束");
}
}
3. 因此作出改进,在关闭资源前进行in 是否为null的判断,这样看起来比较合理,测试解决了null问题,但是随即又发现了一个新问题,readFile方法因为in.close()可能存在IOException异常,因此要么在finally语句中加trycatch解决,要么进行声明,这里我们选择了声明,是否发现越来越麻烦?
public class Test
{
public void readFile(String name) throws IOException
{
File file = new File(name);
FileInputStream in = null; //变量定义在try之前,这样finally才能调用
try
{
in= new FileInputStream(file); // 可能产生FileNotFoundException异常
System.out.println("文件流产生,文件存在");
int b;
while ((b = in.read()) != -1) // 可能产生IOException异常
{
System.out.println("读取文件中");
}
}
catch (FileNotFoundException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.println(e.getMessage());
}
finally
{
if(in !=null)
{
in.close();
}
System.out.println("不管异常,最终都要执行关闭资源方法");
}
System.out.println("方法结束");
}
}
4. 因此我们可以灵活使用try finally语句和try catch 语句,内部try finally语句进行文件处理并关闭资源,外围try catch语句确保处理任何的异常,这样就可以很大程度提高代码清晰度和逻辑性
public class Test
{
public void readFile(String name)
{
try
{
File file = new File(name);
FileInputStream in =null;
try
{
in = new FileInputStream(file);
int b;
while ((b = in.read()) != -1)
{
System.out.println("读取文件中");
}
}
finally
{
if(in !=null)
{
in.close(); //有可能的异常被外围的try catch语句给捕获解决了
}
System.out.println("关闭资源");
}
}
catch (FileNotFoundException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.println(e.getMessage());
}
System.out.println("方法结束");
}
}
5. JAVA中针对这类带有资源读取产生异常的情况,有一个特殊的办法,就是带资源的try语句,这样的try语句不管是否异常都会在结束方法时关闭资源(实际编程中第4点和第5点都可以使用,看个人习惯)
public class Test
{
public void readFile(String name)
{
File file = new File(name);
try (FileInputStream in = new FileInputStream(file))
{
int b;
while ((b = in.read()) != -1)
{
System.out.println("读取文件中");
}
}
catch (FileNotFoundException e)
{
System.out.println(e.getMessage());
}
catch (IOException e)
{
System.out.println(e.getMessage());
}
System.out.println("方法结束");
}
}
九、异常的特别注意点
1)不要过分细分异常:有可能产生异常的语句可以放在一个try语句中,没有必要去分几个try
如下面代码就是不建议的:
public class Test
{
public void readFile(String name)
{
File file = new File(name);
try
{
FileInputStream in = new FileInputStream(file);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
try
{
Class class1 = Class.forName(name);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
System.out.println("方法结束");
}
}
应该修改为:
public class Test
{
public void readFile(String name)
{
File file = new File(name);
try
{
FileInputStream in = new FileInputStream(file);
Class class1 = Class.forName(name);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
System.out.println("方法结束");
}
}
2)利用异常层次结构:抛出异常要抛出更详细的异常,不要只抛出RuntimeException这类的不方便精确定位问题
如这个明明是FileNotFoundException的异常,但是却捕获它的父类异常,这是不合理的
public class Test
{
public void readFile(String name)
{
File file = new File(name);
try
{
FileInputStream in = new FileInputStream(file);
}
catch (IOException e)
{
e.printStackTrace();
}
System.out.println("方法结束");
}
}
3)早抛出:遇到异常时抛出该异常比抛出空对象导致后面引起空异常好
4)晚捕获:遇到异常时不要全部捕获解决,有的时候应该抛出异常让更高层次的调用者通知错误发生(个人理解是service层产生异常时应该传递,到controller层在处理控制?)