Java中的异常处理机制

2017-01-13 15:08:06来源:csdn作者:qq_37267015人点击

第七城市





学习Java的同学注意了!!!学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990 我们一起学Java!



异常机制已经成为判断一门编程语言是否成熟的标准,异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。



Java异常机制主要依赖于try、catch、finally、throw、throws五个关键字。



1.try:它里面放置可能引发异常的代码



2.catch:后面对应异常类型和一个代码块,用于表明该catch块用于处理这种类型的代码块,可以有多个catch块。



3.finally:主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件),异常机制总是保证finally块总是被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者 throw等终止方法的语句,则就不会跳回执行,直接停止。



4.throw:用于抛出一个实际的异常,可以单独作为语句使用,抛出一个具体的异常对象。



5.throws:用在方法签名中,用于声明该方法可能抛出的异常。





Java的异常分为两种,checked异常(编译时异常)和Runtime异常(运行时异常)



1. java认为checked异常都是可以再编译阶段被处理的异常,所以它强制程序处理所有的checked异常,而Runtime异常无须处理,java程序必须显式处理checked异常,如果程序没有处理,则在编译时会发生错误,无法通过编译。



2. checked异常体现了java设计哲学:没有完善处理的代码根本不会被执行,体现了java的严谨性,



对于构造大型、健壮、可维护的应用系统而言,错误处理是整个应用需要考虑的重要方面。Java异常处理机制,在程序运行出现意外时,系统会生成一个Exception对象,来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。



如果执行try块里的业务逻辑代码时出现异常,系统会自动生成一个异常对象,该异常对象被提交给运行环境,这个过程被称为抛出(throw)异常。Java环境收到异常对象时,会寻找合适的catch块,如果找不到,java运行环境就会终止,java程序将退出。



不同的catch块,视为了针对不同的异常类,提供不同的处理方法。





对于错误处理机制,主要有如下的两个缺点:



1.无法穷举所有异常情况:因为人类的知识是有限的,异常情况总比可以考虑到的情况多,总有漏网之鱼



2.错误处理代码和业务实现代码混杂严重影响程序的可读性,会增加程序维护的难度。



1.使用try...catch捕获异常



java提出了一种假设,如果程序可以顺利完成,那么一切正常,把系统的业务实现代码放在try块中定义,所有的异常处理逻辑放在catch块中进行处理。



即:try{



//业务实现代码



...



}



catch(Exception e){



输入不合法



}



上面的格式中try块和catch块后的{...}都是不可以省略的!



执行步骤:



1.如果执行try块中的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给java运行环境,这个过程称为抛出(throw)异常。



2.当java运行环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的cathc块并把该异常对象交给catch块处理,那这个过程称为捕获(catch)异常;如果java运行时环境找不到捕获异常的catch块,则运行时环境终止,jav程序也将退出。



注意1:不管程序代码块是否处于try块中,甚至包括catch块中代码,只要执行该代码时出现了异常,系统都会自动生成一个异常对象,如果程序没有为这段代码定义任何catch块,java运行环境肯定找不到处理该异常的catch块,程序肯定在此退出。



注意2:try块后可以有多个catch块,try块后使用多个catch块是为了针对不同异常类提供的不同的异常处理方式。当系统发生不同意外情况时,系统会生成不同的异常对象,java运行时就会根据该异常对象所属的异常类来决定使用哪个catch块来处理该异常。



注意3:通常情况下,如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块被执行,除非在循环中使用类continue开始下一次循环,下一次循环又重新运行了try块,这才可能导致多个catch块被执行。



注意4:进行异常捕获时,一定要记住先捕获小的异常,再捕获大的异常。





Java的异常类,以及他们的继承关系:





java把所有非正常情况分成两种:异常(Exception)和错误(Error),都是继承自Throwable父类。



Error错误:一般是指虚拟机相关的问题,如系统崩溃,虚拟机出错误等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常不处理。





Throwable():Throwable类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Javathrow语句抛出。类似地,只有此类或其子类之一才可以是catch子句中的参数类型。



1.Error(错误):一般是指java虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断,通常应用程序无法处理这些错误,因此应用程序不应该捕获Error对象,也无须在其throws子句中声明该方法抛出任何Error或其子类。



2.Exception:Exception类及其子类是Throwable的一种形式,它指出了合理的应用程序想要捕获的条件



(1). SQLException:该异常提供关于数据库访问错误或其他错误的信息。



(2).RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类



(3).IOException:此类为异常的通用类,它是由失败的或中断的 I/O 操作生成的。



异常对象包含的常用方法:



1. getMessage();返回该异常的详细描述字符



2. printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。



3. printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定的输出流



4. getStackTrace():返回该异常的跟踪栈信息。

复制代码
1 public class TestException
2 {
3
4 public static void main(String[] args)
5 {
6
7 try{
8 FileInputStream fis=new FileInputStream("a.txt");
9}
10 catch(IOException ioe)
11 {
12 System.out.println(ioe.getMessage());
13 ioe.printStackTrace();
14 }
15
16 }
17
18 }
复制代码






使用finally回收资源



有时候,程序在try块里面打开了一些物力资源(比如数据库连接,网络连接好磁盘文件等),这些物理资源都必须显式回收。



因为:java的垃圾回收机制不会回收任何的物理资源,垃圾回收机制只回收堆内存中对象所占用的内存。





问题1:那么在哪边回收这些物理资源呢?



答:在finally块中,因为如果try块的某条语句引起一场,该语句后的其他语句通常不会被执行,那将导致位于该语句后的资源回收语句得不到执行;如果在catch块里进行资源回收,但catch块完全有可能得不到执行,这也将导致不能及时回收这些物理资源。所以我们不管try块中的代码是否出现异常,也不管哪个catch块会被执行,finally块总会被执行。



那么:java异常处理的完整语法结构如下:



try{ //业务实现逻辑 ...}catch(SubException e){ //异常处理快1 ...}catch(SubException2 e){ //异常处理快2 ...} ...finally{ //资源回收块 ...}



以上的异常处理语法结构中注意点1:只有try块石必须的,也就是说如果没有try块,则不可能有后面的catch块和finally块;注意点2:catch块和finally块都是可选的,但catch块和finally块至少出现其中之一,也可以同时出现;注意点3:可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;注意点4:不能只有try块,既没有catch块,也没有finally块;注意点5:多个catch块必须位于try块之后,finally块必须位于所有catch块之后。

复制代码
1 import java.io.FileInputStream;
2 import java.io.IOException;
3
4 public class TestException
5 {
6
7 /**
8* @param args
9*/
10 public static void main(String[] args)
11 {
12 // TODO Auto-generated method stub
13 FileInputStream fis = null;
14 try
15 {
16 fis = new FileInputStream("a.txt");
17 } catch (IOException ioe)
18 {
19 System.out.println(ioe.getMessage());
20 // return语句强制方法返回
21 return;
22 // 使用exit来退出虚拟机
23 // System.exit(1);
24 } finally
25 {
26 // 关闭磁盘文件,回收资源
27 if (fis != null)
28 {
29 try
30 {
31 fis.close();
32 } catch (IOException ioe)
33 {
34 ioe.printStackTrace();
35 }
36 }
37 System.out.println("程序已经执行了finally里德资源回收");
38 }
39 }
40
41 }
复制代码


运行程序结果:a.txt (系统找不到指定的文件。)程序已经执行了finally里德资源回收



如果将catch块中的最后两句注释放入程序,那么结果为:a.txt (系统找不到指定的文件。)



以上两种情况显示:除非在try块或者catch块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。不过,一般情况下,不要再finally块中使用renturn或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。





复制代码
1 public class TestException1
2 {
3
4 public static boolean test()
5 {
6 try
7 {
8 return true;
9 } finally
10 {
11 return false;
12 }
13 }
14
15 public static void main(String[] args)
16 {
17 boolean a = test();
18 System.out.println(a);
19 }
20
21 }
复制代码






运行结果:false



以上的小程序说明:在finally块中定义了一个renturn false语句,这将导致try块中的return true 失去作用!



总结一下这个小问题:



当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。如果有finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用再跳回去执行try块、catch块里的任何代码了。



综上:尽量避免在finally块里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况!



异常处理的嵌套



例如catch块中再次包含了一个完整的异常处理流程,这种在try块,catch块或finally块中包含完整的异常处理流程的情形称为异常处理的嵌套。异常处理流程的代码可以放在任何可执行代码的地方,因此完整的异常处理流程既可放在try块,也可放在catch块,也可放在finally块里。



嵌套的深度没有很明确的限制,通常没有必要写层次太深的嵌套异常处理,会导致程序可读性降低。



Checked异常和Runtime异常体系



java异常被分为两大类:Checked异常和Runtime异常(运行时异常)。



所有RuntimeException类及其子类的实例被称为Runtime异常,不是RuntimeException类及其子类的异常实例则被称为Checked异常。



只有java语言提供了Checked异常,其他语言都没有提供,java认为Checked异常都是可以被处理(修复)的异常,所以java程序无须显式的处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。



Checked异常的处理方式:



①:当方法明确知道如何处理异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修补该异常。



②:当方法不知道如何处理异常,应该在定义该方法时声明抛出该异常。



Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch块来捕获Runtime异常。



问题是:大部分的方法总是不能明确知道如何处理异常,这就只能声明抛出异常了。



使用throws抛出异常



使用throws抛出异常的思路是:当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道应该如何处理这种类型的异常,也可以使用使用throws声明抛出异常,该异常将交给JVM来处理。



JVM对异常的处理方法:打印异常跟踪栈的信息,并终止程序运行,所以有很多程序遇到异常后自动结束。



使用throws抛出异常的格式:



throws声明的抛出的语法格式紧跟在方法之后,可以声明多个异常类,多个异常类之间以逗号隔开。一旦使用了throws语句声明抛出异常,就不用再使用try...catch来捕获异常了。



如:throws ExceptionClass1,ExceptionClass2...



注意点1:如果某段代码调用了一个带throws声明的方法,该方法声明抛出了Checked异常,这表明该方法希望它的调用者来处理该异常。那么这段代码要么放在try块中显示捕获该异常,要么这段代码处于另一个带throws声明抛出的方法中。



举例如下:

复制代码
1 //方法一:
2
3 import java.io.FileInputStream;
4 import java.io.IOException;
5
6 public class TestException2
7 {
8
9 // test() 方法抛出了异常,那么test()方法的调用者要么放在try块中显示捕获该异常,要么这段代码处于另一个带throws声明抛出的方法中。
10
11 // 以下为后者的处理方法
12
13 public static void test() throws IOException
14 {
15 FileInputStream fis = new FileInputStream("a.txt");
16 }
17
18 public static void main(String[] args) throws Exception
19 {
20 test();
21 }
22
23 }
复制代码
复制代码
1 //方法二:
2
3 import java.io.FileInputStream;
4 import java.io.IOException;
5
6 public class TestException2
7 {
8
9 public static void test() throws IOException
10 {
11 FileInputStream fis = new FileInputStream("a.txt");
12 }
13
14 public static void main(String[] args)
15 {
16 try
17 {
18 test();
19 } catch (IOException e)
20 {
21 // TODO Auto-generated catch block
22 e.printStackTrace();
23 }
24 }
25
26 }
复制代码


使用throws声明抛出异常时有一个限制:就是方法重写时的“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或或相等,子类方法中不允许比父类方法声明抛出更多异常。即如果子类抛出的异常是父类抛出的异常的父类,那么程序无法通过编译。



因为Checked异常存在一些不便之处,大部分情况,可以使用Runtime异常,如果程序需要在合适的地方捕获异常,并对异常进行处理,程序一样可以用try...catch捕获Runtime异常。



使用throw抛出异常



当程序出现错误时,系统会自动抛出异常,另外,java也允许程序自行抛出异常,自行抛出异常使用throw语句完成!



抛出异常:



如果需要在程序中自行抛出异常,应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。throw语句的格式如下:throw ExceptionInstance;



throw语句抛出异常的两种情况:



1.当throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把异常交给方法的调用者处理。



2.当throw语句抛出的异常是Runtime异常,则该语句无须放在try块内,也无须放在带throws声明抛出的方法中,程序既可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法的调用者处理。



举例如下:

复制代码
1 public class TestException3
2 {
3
4 public static void throwChecked(int a) throws Exception
5 {
6 if (a < 0)
7 {
8 /**
9* 自行抛出Exception异常 改代码必须处于try块里,或处于带throws声明的方法中
10*/
11 throw new Exception("a的值大于0,不符合要求");
12 }
13 }
14
15 public static void throwRuntime(int a)
16 {
17 if (a < 0)
18 {
19 /**
20* 自行抛出RuntimeException异常,既可以显式捕获该异常 也可以完全不用理会该异常,把该异常交给方法的调用者处理
21*/
22 throw new RuntimeException("a的值大于0,不符合要求");
23 } else
24 {
25 System.out.println("a的值为:" + a);
26 }
27 }
28
29 public static void main(String[] args)
30 {
31 try
32 {
33 /**
34* 此处调用了带throws声明的方法,必须显示捕获该异常(使用try...catch) 否则,要在main方法中再次声明抛出
35*/
36 throwChecked(-3);
37 } catch (Exception e)
38 {
39 System.out.println(e.getMessage());
40 }
41 throwRuntime(3);
42 }
43
44 }
复制代码


由上面的代码显式:自行抛出Runtime异常比自行抛出Checked异常的灵活性更好。







自定义异常类



用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。



应以异常类通常需要提供两种构造器:一个是无参数的构造器,另一个是带一个字符串的构造器,这个字符串将作为该异常对象的详细说明(也就是异常对象的getMessage方法的返回值)。



通常情况下,程序会很少自行抛出系统异常,因为异常的类名通常包含了该异常的有用信息,所以在选择抛出什么异常时,应该选择合适的异常类,从而可以明确地描述异常情况,这样程序常常需要定义异常类。



用户定义异常类,需要基础Exception基类,如果希望定义RuntimeException基类,就应该继承该基类,定义异常类时通常需要提供两种构造器:1,无参的构造器,2,带字符串的构造器,这个字符串作为该异常对象的详细说明,(也就是异常对象的getMessage方法返回值),调用super将字符串参数传给异常对象的message属性,message属性就是异常对象的详细描述信息。



例子如下:

复制代码
1 public class TestException4 extends Exception
2 {
3
4 public TestException4()
5 {
6
7 }
8
9 public TestException4(String msg)
10 {
11 super(msg);
12 }
13
14 }
复制代码


catch和throw同时使用



前面已有两种异常处理方法:



1.在异常出现的方法内捕获并处理,方法的调用者将不能再次捕获该异常。



2.该方法签名中声明抛出该异常,将该异常完全交给方法调用者处理。



但是在实际应用中往往需要更复杂的处理方式,即异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,可以让该方法的调用者也能捕获到异常。



为了实现这种靠多个方法协作处理同一个异常的情形,可以通过catch块中结合throw来完成。



举例catch和throw同时使用的例子:

复制代码
1 public class TestException4
2 {
3 // 以下AuctionException这个异常是自定义的异常类
4 private double initPrice = 30.0;
5
6 public void bid(String bidPrice) throws AuctionException
7 {
8 double d = 0.0;
9 try
10 {
11 d = Double.parseDouble(bidPrice);
12 } catch (Exception e)
13 {
14 e.printStackTrace();
15 throw new AuctionException("竞拍价必须是数值,不能包含其他字符!");
16 }
17 if (initPrice > d)
18 {
19 throw new AuctionException("竞拍价比起拍价低,不允许竞拍!");
20 }
21 initPrice = d;
22 }
23
24 public static void main(String[] args)
25 {
26 TestException4 ta = new TestException4();
27 try
28 {
29 ta.bid("df");
30 } catch (AuctionException ae)
31 {
32 // TODO: handle exception
33 System.err.println(ae.getMessage());
34 }
35 }
36 }
复制代码


catch和throw同时使用来处理异常的方法是在大型企业中比较常用的。



java的异常跟踪栈



异常对象的printStackTrace方法用于打印异常的跟踪栈信息,根据printStackTrace方法的输出结果,我们可以找到异常的源头,并跟踪到异常一路触发的过程。



虽然printStackTrace()方法可以很方便地追踪异常的发生状况,可以用它来调试,但是在最后发布的程序中,应该避免使用它。而应该对捕获的异常进行适当的处理,而不是简单的将信息打印出来。



总之,要合理使用异常。




















学习Java的同学注意了!!!学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:183993990 我们一起学Java!


第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台