大道至简,知易行难,很多时候我们并不是不知道怎么做,而是无法坚持。
异常
- 程序运行时,发生了不被期望的结果,阻止了程序按照预期正常执行,这就是异常。世界上没有不出错的程序,只有正确处理好意外情况,才能保证程序的可靠性。
- 异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
- Java中的异常可能是函数中的语句执行时引发的,也可能是程序员通过throw语句手动抛出的,只要Java程序中产生了异常,就需要用一个对应类型的异常对象来封装异常,JRE就会试图寻找相应的异常处理程序来处理异常。
4、Java的异常处理本质上是抛出异常和捕获异常。
public class ExceptionTestDemo {
public static void main(String[] args) {
System.out.println("这是异常之前的程序");
System.out.println(5/0); //除数为0
System.out.println("这是之后的程序");
}
}
运行结果
这是异常之前的程序
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo.ExceptionTestDemo.main(ExceptionTestDemo.java:6)
1、当程序运行到异常处时,程序自动退出,不在执行后面的语句,这样是很危险的。异常处理的作用就是能够使程序出现异常时,让程序尽最大可能恢复正常并继续执行。
2、除数为0这种异常属于免检异常,所以没有捕获和抛出,依然可以通过编译。
3、异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。异常最先发生的地方,叫做异常抛出点。
异常的继承关系
- 在java中,异常系统的核心就是异常本身,异常是一个对象,它们是从Throwable派生而来的类的实例,当异常被触发时,将会创建Throwable类的一个实例。
- Throwable有两个子类:Error和Exception。Error实例是java运行环境(虚拟机)内部的错误,这种错误很少见,但通常是致命的;对于它们,我们将无能为力,提供这个类旨在让java在需要时能够使用他们。
- Exception以及它的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
异常类
异常类主要可以分为两种,一种是免检异常(unckecked exception);一种是必检异常(Checked Exception)。
-
免检异常
定义:免检异常是指RuntimeException 以及其子类
特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。
解决方法:这样的异常发生的原因多半是代码写的有问题。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。 -
必检异常
定义: Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点:Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
异常处理
Java的异常处理本质上是使用throws抛出异常或try-catch捕获异常。
Java异常机制用到的几个关键字:try、catch、finally、throw、throws。
• try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw – 用于抛出异常。
• throws – 用在方法签名中,用于声明该方法可能抛出的异常。
try catch
try-catch格式
try
{
//statements that may cause an exception
}
catch (exception(type) e(object))
{
//error handling code
}
要捕获异常
- 将包含可能引发异常的方法的代码放在try块中。
- 在catch中对异常进行处理。
package ExceptionDemo;
public class ExceptionTestDemo {
public static void main(String[] args) {
System.out.println("这是异常之前的程序");
try{
System.out.println(5/0);
System.out.println("这是异常之后的程序");
}catch(Exception e){
System.out.println("这是catch块中的程序");
e.printStackTrace();//打印异常信息
}
System.out.println("这是最后的程序");
}
}
这是异常之前的程序
这是catch块中的程序
java.lang.ArithmeticException: / by zero
at ExceptionDemo.ExceptionTestDemo.main(ExceptionTestDemo.java:7)
这是最后的程序
- 如果try块没有抛出异常,那么catch块将会被完全忽略,程序照常运行
- 如果try块抛出异常,对应的catch块将会捕获它,catch块中的语句将会被执行,然后程序继续运行
- 异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。e.printStackTrace()为打印异常追踪栈的信息;异常最先发生的地方,叫做异常抛出点。
- catch块中圆括号的内容类似于方法定义中的参数列表,其中包含了将捕获的异常类或变量名,可以使用该变量名来引用异常对象。常用的方法有getMessage()和printStackTrace();
一个try可以对应多个catch
package ExceptionDemo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
public class ExceptionTestDemo {
private static String inputFilePath = ".";
static InputStream testInput = null;
static OutputStream testOutput = null;
public static void main(String[] args) {
System.out.println("这是异常之前的程序");
try {
testInput = new FileInputStream(inputFilePath);//异常:文件找不到
System.out.println(5/0);//除数为0异常
System.out.println("这是异常之后的程序");
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(ArithmeticException e2){
e2.printStackTrace();
}
System.out.println("这是最后的程序");
}
}
运行结果
这是异常之前的程序
java.io.FileNotFoundException: . (拒绝访问。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(Unknown Source)
at java.io.FileInputStream.<init>(Unknown Source)
at java.io.FileInputStream.<init>(Unknown Source)
at ExceptionDemo.ExceptionTestDemo.main(ExceptionTestDemo.java:18)
这是最后的程序
- 程序会根据发生的异常和catch里面的进行匹配(按照catch块从上往下匹配),当它匹配某一个catch块的时候,他就直接进入到这个catch块里面去了,后面在再有catch块的话,它不做任何处理,直接跳过去,全部忽略掉。
- try-catch一次只能捕获一个异常
- 解决方法:针对可能出现异常的地方,单独try-catch。
finally
假设不管发生什么情况,也无论异常是否被引发,代码中的一些操作都必须要执行。这通常是释放外部资源,关闭打开的文件等
finally 格式
try {
} catch (Exception e) {
// TODO: handle exception
}finally {
}
或者
try {
} finally {
}
无论异常是否发生,finally的内容都将被执行,该语句也可用于循环的return,break,或continue之后。
package ExceptionDemo;
public class finallyDemo {
static String judgePrime(int num){
try {
for(int i=2;i<=Math.sqrt(num);i++){
if(num%2==0){
return "这是一个合数";
}
}
return "这是一个素数";
} finally {
System.out.println("这是finally语句中的");
}
}
public static void main(String[] args) {
System.out.println(judgePrime(2));
}
}
执行结果
这是finally语句中的
这是一个素数
当上述情况遇到return 语句时,try-catch将导致一种不同寻常的情况,你可能认为return将导致judgePrime()方法立即结束,但是由于它处于try-catch块中,因此无论try块是如何结束的,finally块中的语句都将被执行。
声明可能引发异常的方法
- 前面的例子介绍了如何处理异常,通过try-catch进行代码保护,并捕获可能发生的异常,java编译器将检查并确保你对方法的异常进行了处理,但他是如何知道需要处理哪些异常那?
- 举个例子:当你使用输入流时,如果文件不存在,则会报FileNotFoundException,但是如果文件存在,则没有异常,异常并非一定发生,而是可能发生,所以异常处理应该交给使用者来处理,为了安全性,java编译器强制性要求使用者对这种可能的异常进行处理,但是java编译器如何知道,你要使用的方法有可能产生哪些异常呢?答案是方法在特征标中指出将可能发生哪些异常,我们也可以在我们的方法中使用这种标志–实际上,这样做是非常好的,我们可以告诉使用我们写的方法的用户,我们的哪些方法可能产生哪些异常。
throws
格式
public void myMethod(int x,int y)throws ArithmeticException{
}
当然与catch 对应,如果可能引发多个异常,也可以将它们放在一个throws语句中,并用逗号分隔。
public void myMethod(int x,int y)throws ArithmeticException,FileNotFoundException{
}
当然也可以像catch一样,使用超类来指出可能引发这种异常的所有子类
public void myMethod(int x,int y)throws IOException{
}
- 将throws加入到方法定义只是意味着:如果错误,该方法可能引发异常,而并不是一定发生这种情况,throws子句只是在方法定义提供了有关潜在异常的信息,并让java能够确保其他人正确的使用该方法
- 通过使用throws,可以添加一些信息,指出该方法能执行的非常规工作,约定中的这部分内容有助于显示的指出应在程序中哪些地方对异常情况进行处理,从而简化大规模的设计工作。
- 无需列出可能引发的所有异常,如无需列出Error,RuntimeException(或其子类)的异常,这些将有java本身环境本身引发。
- 除了声明引发异常的方法外,还有一种情况,方法定义也可能包含throws语句,当我们想要使用一个可能引发异常的方法,但是我们又不想捕获或者处理该异常。这样使用throws子句来声明该方法,使之也可能引发合适的异常,这样处理异常的工作将由调用我们的方法的方法来处理。
throws和继承
当我们想要覆盖父类的可能引发异常的方法时,我们所写的方法没必要非得与被覆盖的方法完全一致。
class A{
public void C()throws FileNotFoundException,EOFException{
}
}
class B extends A{
public void C()throws FileNotFoundException{
//覆盖父类的C方法,但异常声明并不一定要和父类完全一致,甚至可以不引发异常
//这不同于方法特征标的其他部分,必须模仿被覆盖的方法。
}
}
子类的方法可引发更少的异常,甚至不引发异常,但是子类方法不能引发父类方法没有引发的异常。
引发异常
- 声明我们的方法可能引发异常只对该方法的用户和java编译器有用,java编译器将确保所有的异常被处理,但声明本身并不引发异常,我们必须在方法中进行引发
- 异常都是某个异常类的实例,标准java类库中定义了很多这样的类,要引发异常,必须创建改异常类的实例,然后使用throw语句来引发它。
- 只能引发Throwable子类的对象。
FileNotFoundException e1 = new FileNotFoundException();
throw e1;
或者
FileNotFoundException e1 = new FileNotFoundException("异常的描述信息");
throw e1;
异常被引发后,方法立即结束,而不执行任何其他代码(除finally中的代码外,如果有的话),也不会返回值。如果在调用该方法时,没有用try-catch捕获或者继续抛出该异常,则程序则直接退出。
创建自己的异常
- 虽然java类库中有很多异常可以供我们选择在自己的方法中使用,但我们仍然需要创建自己的异常来处理程序中可能出现的各种错误。
- 新异常应该继承java层次结构中的某个异常。所有用户创建的异常都应该是Exception而非Error层次结构中的一部分,Error用于表示涉及java虚拟机的错误。找到一个与要创建的异常相近的异常:例如,文件格式不对的异常应该是IOException的子类,如果找不到与要创建的异常密切相关的异常,可考虑继承Exception。
- 异常类通常有两个构造函数:第一个接受不接受参数,第二个接受一个字符串参数,第二种构造函数,应在其中调用super.();以确保字符串被放到正确的地方。
class MyException extends IOException{
public MyException(){
}
public MyException(String s){
super(s);
}
}
创建自己的异常之后,我们便可以使用自己创建的异常类建立新的异常对象。
组合使用try throws throw
下面是一个组合使用的案例:
在很多情况下,我们需要的数据有一定的格式,假设我们需要的数必须是素数,如果不是素数,则不能使用,当数据不是素数时,便产生异常。
package ExceptionDemo;
import java.io.IOException;
class MyException extends IOException{
private static final long serialVersionUID = 1L;
//这是我们创建的异常类
public MyException(String s){
super(s);
}
public MyException(){
}
}
public class throwsDemo {
public static void judgePrime(int num)throws MyException{
for(int i=2;i<Math.sqrt(num);i++){
if(num%i==0)
throw new MyException("这不是一个素数");
}
}
public static void main(String[] args) {
int k =6;
try {
judgePrime(k);
} catch (MyException e) {
System.out.println("结果不是素数,我们需要做一些事情处理");
e.printStackTrace();
}
System.out.println("程序不会终止,继续运行");
}
}
运行结果
结果不是素数,我们需要做一些事情处理
ExceptionDemo.MyException: 这不是一个素数
at ExceptionDemo.throwsDemo.judgePrime(throwsDemo.java:16)
at ExceptionDemo.throwsDemo.main(throwsDemo.java:22)
程序不会终止,继续运行