Java的异常处理惩罚及应用
副标题#e#
Java 异常处理惩罚是利用 Java 语言举办软件开拓和测试剧本开拓时不容忽视的问题之一,是否举办异常处理惩罚直接干系到开拓出的软件的不变性和结实性。本文系统的叙述了 Java 异常处理惩罚的道理和要领,并罗列了一些实例,使读者对 Java 异常处理惩罚能有一个全面的认识,领略异常处理惩罚机制,能越发机动和有效地在开拓中利用它。
Java 异常处理惩罚引出
假设您要编写一个 Java 措施,该措施读入用户输入的一行文本,并在终端显示该文本。
措施如下:
1 import java.io.*; 2 public class EchoInput { 3 public static void main(String args[]){ 4 System.out.println("Enter text to echo:"); 5 InputStreamReader isr = new InputStreamReader(System.in); 6 BufferedReader inputReader = new BufferedReader(isr); 7 String inputLine = inputReader.readLine(); 8 System.out.println("Read:" + inputLine); 9 } 10 }
阐明上面的代码,在 EchoInput 类中,第 3 行声明白 main 要领;第 4 行提示用户输入文本;第 5、6 行配置 BufferedReader 对像毗连到 InputStreamReader,而 InputStreamReader 又毗连到尺度输入流 System.in;第 7 行读入一行文本;第 8 行用尺度输出流 System.out 显示出该文本。
外貌看来上面的措施没有问题,但实际上,EchoInput 类完全大概呈现问题。要在挪用第 7 行的 readLine 要领时正确读取输入,这几种假设都必需创立:假定键盘有效,键盘能与计较机正常通信;假定键盘数据可从操纵系统传输到 Java 虚拟机,又从 Java 虚拟机传输 inputReader。
大大都环境下上述假设都创立,但不尽然。为此,Java 回收异常要领,以应对大概呈现的错误,并采纳步调举办矫正。在本例中,若试图编译以上代码,将看到以下信息:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException at EchoInput.main(EchoInput.java:7)
从中可以看到,第 7 行挪用 readLine 要领大概堕落:若果然如此,则发生 IOException 来记录妨碍。编译器错误是在汇报您,需要变动代码来办理这个潜在的问题。在 JDK API 文档中,可以看到同样的信息。我们可以看到 readLine 要领,如图 1 所示。
图 1. BufferedReader 类的 readLine 要领的 JDK API 文档
由图 1 可知,readLine 要领有时发生 IOException。如那里理惩罚潜在的妨碍?编译器需要“捕捉”或“声明”IOException。
“捕捉 (catch)”指当 readLine 要领发生错误时截获该错误,并处理惩罚和记录该问题。而“声明 (declare)”指错误大概激发 IOException,并通知挪用该要领的任何代码:大概发生异常。
若要捕捉异常,必需添加一个非凡的“处理惩罚代码块”,来吸收和处理惩罚 IOException。于是措施改为如下:
1 import java.io.*; 2 public class EchoInputHandle { 3 public static void main(String args[]){ 4 System.out.println("Enter text to echo:"); 5 InputStreamReader isr = new InputStreamReader(System.in); 6 BufferedReader inputReader = new BufferedReader(isr); 7 try{ 8 String inputLine = inputReader.readLine(); 9 System.out.println("Read:" + inputLine); 10 } 11 catch(IOException exc){ 12 System.out.println(“Exception encountered: ” + exc); 13 } 14 } 15 }
新添的代码块包括要害字 try 和 catch(第 7,10,11,13 行),暗示要读取输入。若乐成,则正常运行。若读取输入时错误,则捕捉问题(由 IOException 工具暗示),并采纳相应法子。在本例,回收的处理惩罚方法是输出异常。
若禁绝备捕捉 IOException,仅声明异常,则要出格指定 main 要领大概堕落,并且出格说明大概发生 IOException。于是措施改为如下:
1 import java.io.*; 2 public class EchoInputDeclare { 3 public static void main(String args[]) throws IOException{ 4 System.out.println("Enter text to echo:"); 5 InputStreamReader isr = new InputStreamReader(System.in); 6 BufferedReader inputReader = new BufferedReader(isr); 7 String inputLine = inputReader.readLine(); 8 System.out.println("Read:" + inputLine); 9 } 10 }
从上面的这个简朴的例子中,我们可以看出异常处理惩罚在 Java 代码开拓中不能被忽视。
Java 异常以及异常处理惩罚
可将 Java 异常看作是一类动静,它传送一些系统问题、妨碍及未按划定执行的行动的相关信息。异常包括信息,以将信息从应用措施的一部门发送到另一部门。
#p#分页标题#e#
编译语言为何要处理惩罚异常?为何不在异常呈现位置随时处理惩罚详细妨碍?因为有时候我们需要在系统中交换错误动静,以便凭据统一的方法处理惩罚问题,有时是因为有若干处理惩罚问题的大概方法,但您不知道利用哪一种,此时,可将处理惩罚异常的任务委托给挪用要领的代码。挪用者凡是更能相识问题来历的上下文,能更好简直定规复方法。
图 2 是一个通用动静架构。
图 2. 通用动静架构
从上图可以看出,肯定在运行的 Java 应用措施的一些类或工具中发生异常。呈现妨碍时,“发送者”将发生异常工具。异常大概代表 Java 代码呈现的问题,也大概是 JVM 的相应错误,或基本硬件或操纵系统的错误。
异常自己暗示动静,指发送者传给吸收者的数据“负荷”。首先,异常基于类的范例来传输有用信息。许多环境下,基于异常的类既能识别妨碍本因并能矫正问题。其次,异常还带有大概有用的数据(如属性)。
在处理惩罚异常时,动静必需有吸收者;不然将无法处理惩罚发生异常的底层问题。
在上例中,异常“发生者”是读取文本行的 BufferedReader。在妨碍呈现时,将在 readLine 要领中构建 IOException 工具。异常“吸收者”是代码自己。EchoInputHandle 应用措施的 try-catch 布局中的 catch 块是异常的吸收者,它以字符串形式输出异常,将问题记录下来。
Java 异常类的条理布局
在我们从总体上相识异常后,我们应该相识如安在 Java 应用措施中利用异常,即需要相识 Java 类的条理布局。图 3 是 Java 类的条理布局图。
图 3. Java 类的条理布局
在 Java 中,所有的异常都有一个配合的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常流传机制通过 Java 应用措施传输的任何问题的共性。
Throwable 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理惩罚的重要子类,各自都包括大量子类。
Exception(异常)是应用措施中大概的可预测、可规复问题。一般大大都异常暗示中度到轻度的问题。异常一般是在特定情况下发生的,凡是呈此刻代码的特定要领和操纵中。在 EchoInput 类中,当试图挪用 readLine 要领时,大概呈现 IOException 异常。
Error(错误)暗示运行应用措施中较严重问题。大大都错误与代码编写者执行的操纵无关,而暗示代码运行时 JVM(Java 虚拟机)呈现的问题。譬喻,当 JVM 不再有继承执行操纵所需的内存资源时,将呈现 OutOfMemoryError。
Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类暗示“JVM 常用操纵”激发的错误。譬喻,若试图利用空值工具引用、除数为零或数组越界,则别离激发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
#p#副标题#e#
Java 异常的处理惩罚
在 Java 应用措施中,对异常的处理惩罚有两种方法:处理惩罚异常和声明异常。
处理惩罚异常:try、catch 和 finally
若要捕捉异常,则必需在代码中添加异常处理惩罚器块。这种 Java 布局大概包括 3 个部门,
都有 Java 要害字。下面的例子中利用了 try-catch-finally 代码布局。
1 import java.io.*; 2 public class EchoInputTryCatchFinally { 3 public static void main(String args[]){ 4 System.out.println("Enter text to echo:"); 5 InputStreamReader isr = new InputStreamReader(System.in); 6 BufferedReader inputReader = new BufferedReader(isr); 7 try{ 8 String inputLine = inputReader.readLine(); 9 System.out.println("Read:" + inputLine); 10 } 11 catch(IOException exc){ 12 System.out.println("Exception encountered: " + exc); 13 } 14 finally{ 15 System.out.println("End. "); 16 } 17 } 18}
个中:
try 块:将一个可能多个语句放入 try 时,则暗示这些语句大概抛出异常。编译器知道大概要产生异常,于是用一个非凡布局评估块内所有语句。
catch 块:当问题呈现时,一种选择是界说代码块来处理惩罚问题,catch 块的目标便在于此。catch 块是 try 块所发生异常的吸收者。根基道理是:一旦生成异常,则 try 块的执行中止,JVM 将查找相应的 JVM。
#p#分页标题#e#
finally 块:还可以界说 finally 块,无论运行 try 块代码的功效如何,该块内里的代码必然运行。在常见的所有情况中,finally 块都将运行。无论 try 块是否运行完,无论是否发生异常,也无论是否在 catch 块中得处处理惩罚,finally 块都将执行。
try-catch-finally 法则:
必需在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。
必需遵循块顺序:若代码同时利用 catch 和 finally 块,则必需将 catch 块放在 try 块之后。
catch 块与相应的异常类的范例相关。
一个 try 块大概有多个 catch 块。若如此,则执行第一个匹配块。
可嵌套 try-catch-finally 布局。
在 try-catch-finally 布局中,可从头抛出异常。
除了下列环境,总将执行 finally 做为竣事:JVM 过早终止(挪用 System.exit(int));在 finally 块中抛出一个未处理惩罚的异常;计较机断电、失火、或遭遇病毒进攻。
声明异常
若要声明异常,则必需将其添加到要领签名块的竣事位置。下面是一个实例:
public void errorProneMethod(int input) throws java.io.IOException { //Code for the method,including one or more method //calls that may produce an IOException }
这样,声明的异常将传给要领挪用者,并且也通知了编译器:该要领的任何挪用者必需遵守处理惩罚或声明法则。声明异常的法则如下:
必需声明要领可抛出的任何可检测异常(checked exception)。
非检测性异常(unchecked exception)不是必需的,可声明,也可不声明。
挪用要领必需遵循任何可检测异常的处理惩罚和声明法则。若包围一个要领,则不能声明与包围要领差异的异常。声明的任何异常必需是被包围要领所声明异常的同类或子类。
Java 异常处理惩罚的分类
Java 异常可分为可检测异常,非检测异常和自界说异常。
可检测异常
可检测异常经编译器验证,对付声明抛出异常的任何要领,编译器将强制执行处理惩罚或声明法则,譬喻:sqlExecption 这个异常就是一个检测异常。你毗连 JDBC 时,不捕获这个异常,编译器就通不外,不答允编译。
非检测异常
非检测异常不遵循处理惩罚或声明法则。在发生此类异常时,不必然非要采纳任何适当操纵,编译器不会查抄是否已办理了这样一个异常。譬喻:一个数组为 3 个长度,当你利用下标为3时,就会发生数组下标越界异常。这个异常 JVM 不会举办检测,要靠措施员来判定。有两个主要类界说非检测异常:RuntimeException 和 Error。
Error 子类属于非检测异常,因为无法预知它们的发生时间。若 Java 应用措施内存不敷,则随时大概呈现 OutOfMemoryError;起因一般不是应用措施的非凡挪用,而是 JVM 自身的问题。别的,Error 一般暗示应用措施无法办理的严重问题。
RuntimeException 类也属于非检测异常,因为普通 JVM 操纵激发的运行时异常随时大概产生,此类异常一般是由特定操纵激发。但这些操纵在 Java 应用措施中会频繁呈现。因此,它们不受编译器查抄与处理惩罚或声明法则的限制。
自界说异常
自界说异常是为了暗示应用措施的一些错误范例,为代码大概产生的一个或多个问题提供新寄义。可以显示代码多个位置之间的错误的相似性,也可以区分代码运行时大概呈现的相似问题的一个可能多个错误,或给出应用措施中一组错误的特定寄义。譬喻,对行罗列办操纵时,有大概呈现两种环境:空行列时试图删除一个元素;满行列时试图添加一个元素。则需要自界说两个异常来处理惩罚这两种环境。
Java 异常处理惩罚的原则和隐讳
Java 异常处理惩罚的原则
尽大概的处理惩罚异常
要尽大概的处理惩罚异常,假如条件确实不答允,无法在本身的代码中完成处理惩罚,就思量声明异常。假如工钱制止在代码中处理惩罚异常,仅出声明,则是一种错误和依赖的实践。
详细问题详细办理
异常的部门利益在于能为差异范例的问题提供差异的处理惩罚操纵。有效异常处理惩罚的要害是识别特定妨碍场景,并开拓办理此场景的特定相应行为。为了充实操作异常处理惩罚本领,需要为特定范例的问题构建特定的处理惩罚器块。
记录大概影响应用措施运行的异常
至少要采纳一些永久的方法,记录下大概影响应用措施操纵的异常。抱负环境下,虽然是在第一时间办理激发异常的根基问题。不外,无论回收哪种处理惩罚操纵,一般总应记录下潜在的要害问题。别看这个操纵很简朴,但它可以辅佐您用很少的时间来跟踪应用措施中巨大问题的起因。
按照景象将异常转化为业务上下文
#p#分页标题#e#
若要通知一个应用措施特有的问题,有须要将应用措施转换为差异形式。若用业务特定状态暗示异常,则代码更易维护。从某种意义上讲,无论何时将异常传到差异上下文(即另一技能层),都应将异常转换为对新上下文有意义的形式。
Java 异常处理惩罚的隐讳
一般不要忽略异常
在异常处理惩罚块中,一项最危险的流动是“不加告示”地处理惩罚异常。如下例所示:
1 try{ 2 Class.forName("business.domain.Customer"); 3 } 4 catch (ClassNotFoundException exc){}
常常可以或许在代码块中看到雷同的代码块。有人总喜欢在编写代码时简朴快速地编写空处理惩罚器块,并“自我慰藉地”宣称筹备在“后期”添加规复代码,但这个“后期”酿成了“无期”。
这种做法有什么弊端?假如异常对应用措施的其他部门确实没有任何负面影响,这未尝不行。但事实往往并非如此,异常会扰乱应用措施的状态。此时,这样的代码无异于掩耳盗铃。
这种做法若影响较轻,则应用措施大概呈现独特行为。譬喻,应用措施配置的一个值不见了, 或 GUI 失效。若问题严重,则应用措施大概会呈现重大问题,因为异常未记录原始妨碍点,难以处理惩罚,如反复的 NullPointerExceptions。
假如采纳法子,记录了捕捉的异常,则不行能碰着这个问题。实际上,除非确认异常对代码其余部门绝无影响,至少也要作记录。进一步讲,永远不要忽略问题;不然,风险很大,在后期会引举事以预料的效果。
不要利用包围式异常处理惩罚块
另一个危险的处理惩罚是包围式处理惩罚器(blanket handler)。该代码的根基布局如下:
1 try{ 2 // … 3 } 4 catch(Exception e){ 5 // … 6 }
利用包围式异常处理惩罚块有两个前提之一:
1. 代码中只有一类问题。
这大概正确,但即便如此,也不该利用包围式异常处理惩罚,捕捉更详细的异常形式有利物弊。
2. 单个规复操纵始终合用。
这险些绝对错误。险些没有哪个要领能放之四海而皆准,能应对呈现的任何问题。
阐明下这样编写代码将产生的环境。只要要领不绝抛出预期的异常集,则一切正常。可是,假如抛出了未预推测的异常,则无法看到要采纳的操纵。当包围式处理惩罚器对新异常类执行千篇一律的任务时,只能间接看到异常的处理惩罚功效。假如代码没有打印或记录语句,则基础看不到功效。
更糟糕的是,今世码产生变革时,包围式处理惩罚器将继承浸染于所有新异常范例,并以沟通方法处理惩罚所有范例。
一般不要把特定的异常转化为更通用的异常
将特定的异常转换为更通用异常时一种错误做法。一般而言,这将打消异常起初抛出时发生的上下文,在将异常传到系统的其他位置时,将更难处理惩罚。见下例:
1 try{ 2 // Error-prone code 3 } 4 catch(IOException e){ 5 String msg = "If you didn ’ t have a problem before,you do now!"; 6 throw new Exception(msg); 7 }
因为没有原始异常的信息,所以处理惩罚器块无法确定问题的起因,也不知道如何矫正问题。
不要处理惩罚可以或许制止的异常
对付有些异常范例,实际上基础不必处理惩罚。凡是运行时异常属于此类领域。在处理惩罚空指针可能数据索引等问题时,不必求助于异常处理惩罚。
Java 异常处理惩罚的应用实例
在界说银行类时,若取钱数大于余额时需要做异常处理惩罚。
界说一个异常类 insufficientFundsException。取钱(withdrawal)要领中大概发生异常,条件是余额小于取额。
处理惩罚异常在挪用 withdrawal 的时候,因此 withdrawal 要领要声明抛出异常,由上一级要领挪用。
异常类:
class InsufficientFundsExceptionextends Exception{ private Bank excepbank; // 银行工具 private double excepAmount; // 要取的钱 InsufficientFundsException(Bank ba, double dAmount) { excepbank=ba; excepAmount=dAmount; } public String excepMessage(){ String str="The balance is"+excepbank.balance + "\n"+"The withdrawal was"+excepAmount; return str; } }// 异常类
银行类:
class Bank{ double balance;// 存款数 Bank(double balance){this.balance=balance;} public void deposite(double dAmount){ if(dAmount>0.0) balance+=dAmount; } public void withdrawal(double dAmount) throws InsufficientFundsException{ if (balance<dAmount) throw new InsufficientFundsException(this, dAmount); balance=balance-dAmount; } public void showBalance(){ System.out.println("The balance is "+(int)balance); } }
#p#分页标题#e#
前端挪用:
public class ExceptionDemo{ public static void main(String args[]){ try{ Bank ba=new Bank(50); ba.withdrawal(100); System.out.println("Withdrawal successful!"); }catch(InsufficientFundsException e) { System.out.println(e.toString()); System.out.println(e.excepMessage()); } } }