共计 19972 个字符,预计需要花费 50 分钟才能阅读完成。
Java 异样架构与异样关键字
Java 异样简介
Java 异样是 Java 提供的一种辨认及响应谬误的一致性机制。
Java 异样机制能够使程序中异样解决代码和失常业务代码拆散,保障程序代码更加优雅,并进步程序健壮性。在无效应用异样的状况下,异样能清晰的答复 what, where, why 这 3 个问题:异样类型答复了“什么”被抛出,异样堆栈跟踪答复了“在哪”抛出,异样信息答复了“为什么”会抛出。
Java 异样架构
- Throwable
Throwable 是 Java 语言中所有谬误与异样的超类。
Throwable 蕴含两个子类:Error(谬误)和 Exception(异样),它们通常用于批示产生了异常情况。
Throwable 蕴含了其线程创立时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。 - Error(谬误)
定义:Error 类及其子类。程序中无奈解决的谬误,示意运行应用程序中呈现了重大的谬误。
特点:此类谬误个别示意代码运行时 JVM 呈现问题。通常有 Virtual MachineError(虚拟机运行谬误)、NoClassDefFoundError(类定义谬误)等。比方 OutOfMemoryError:内存不足谬误;StackOverflowError:栈溢出谬误。此类谬误产生时,JVM 将终止线程。
这些谬误是不受检异样,非代码性谬误。因而,当此类谬误产生时,应用程序不应该去解决此类谬误。依照 Java 常规,咱们是不应该实现任何新的 Error 子类的! - Exception(异样)
程序自身能够捕捉并且能够解决的异样。Exception 这种异样又分为两类:运行时异样和编译时异样。
运行时异样
定义:RuntimeException 类及其子类,示意 JVM 在运行期间可能呈现的异样。
特点:Java 编译器不会查看它。也就是说,当程序中可能呈现这类异样时,假使既 ” 没有通过 throws 申明抛出它 ”,也 ” 没有用 try-catch 语句捕捉它 ”,还是会编译通过。比方 NullPointerException 空指针异样、ArrayIndexOutBoundException 数组下标越界异样、ClassCastException 类型转换异样、ArithmeticExecption 算术异样。此类异样属于不受检异样,个别是由程序逻辑谬误引起的,在程序中能够抉择捕捉解决,也能够不解决。尽管 Java 编译器不会查看运行时异样,然而咱们也能够通过 throws 进行申明抛出,也能够通过 try-catch 对它进行捕捉解决。如果产生运行时异样,则须要通过批改代码来进行防止。例如,若会产生除数为零的状况,则须要通过代码防止该状况的产生!
RuntimeException 异样会由 Java 虚拟机主动抛出并主动捕捉(就算咱们没写异样捕捉语句运行时也会抛出谬误!!),此类异样的呈现绝大数状况是代码自身有问题应该从逻辑下来解决并改良代码。
编译时异样
定义: Exception 中除 RuntimeException 及其子类之外的异样。
特点: Java 编译器会查看它。如果程序中呈现此类异样,比方 ClassNotFoundException(没有找到指定的类异样),IOException(IO 流异样),要么通过 throws 进行申明抛出,要么通过 try-catch 进行捕捉解决,否则不能通过编译。在程序中,通常不会自定义该类异样,而是间接应用零碎提供的异样类。该异样咱们必须手动在代码里增加捕捉语句来解决该异样。
- 受检异样与非受检异样
Java 的所有异样能够分为受检异样(checked exception)和非受检异样(unchecked exception)。
受检异样
编译器要求必须解决的异样。正确的程序在运行过程中,常常容易呈现的、合乎预期的异常情况。一旦产生此类异样,就必须采纳某种形式进行解决。除 RuntimeException 及其子类外,其余的 Exception 异样都属于受检异样。编译器会查看此类异样,也就是说当编译器查看到利用中的某处可能会此类异样时,将会提醒你解决本异样——要么应用 try-catch 捕捉,要么应用办法签名中用 throws 关键字抛出,否则编译不通过。
非受检异样
编译器不会进行查看并且不要求必须解决的异样,也就说当程序中呈现此类异样时,即便咱们没有 try-catch 捕捉它,也没有应用 throws 抛出该异样,编译也会失常通过。该类异样包含运行时异样(RuntimeException 极其子类)和谬误(Error)。
Java 异样关键字
• try – 用于监听。将要被监听的代码(可能抛出异样的代码) 放在 try 语句块之内,当 try 语句块内产生异样时,异样就被抛出。
• catch – 用于捕捉异样。catch 用来捕捉 try 语句块中产生的异样。
• finally – finally 语句块总是会被执行。它次要用于回收在 try 块里关上的物力资源(如数据库连贯、网络连接和磁盘文件)。只有 finally 块,执行实现之后,才会回来执行 try 或者 catch 块中的 return 或者 throw 语句,如果 finally 中应用了 return 或者 throw 等终止办法的语句,则就不会跳回执行,间接进行。
• throw – 用于抛出异样。
• throws – 用在办法签名中,用于申明该办法可能抛出的异样。
Java 异样解决
Java 通过面向对象的办法进行异样解决,一旦办法抛出异样,零碎主动依据该异样对象寻找适合异样处理器(Exception Handler)来解决该异样,把各种不同的异样进行分类,并提供了良好的接口。在 Java 中,每个异样都是一个对象,它是 Throwable 类或其子类的实例。当一个办法出现异常后便抛出一个异样对象,该对象中蕴含有异样信息,调用这个对象的办法能够捕捉到这个异样并能够对其进行解决。Java 的异样解决是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
在 Java 利用中,异样的解决机制分为申明异样,抛出异样和捕捉异样。
申明异样
通常,应该捕捉那些晓得如何解决的异样,将不晓得如何解决的异样持续传递上来。传递异样能够在办法签名处应用 throws 关键字申明可能会抛出的异样。
留神
非查看异样(Error、RuntimeException 或它们的子类)不可应用 throws 关键字来申明要抛出的异样。
一个办法呈现编译时异样,就须要 try-catch/ throws 解决,否则会导致编译谬误。
抛出异样
如果你感觉解决不了某些异样问题,且不须要调用者解决,那么你能够抛出异样。
throw 关键字作用是在办法外部抛出一个 Throwable 类型的异样。任何 Java 代码都能够通过 throw 语句抛出异样。
捕捉异样
程序通常在运行之前不报错,然而运行后可能会呈现某些未知的谬误,然而还不想间接抛出到上一级,那么就须要通过 try…catch…的模式进行异样捕捉,之后依据不同的异常情况来进行相应的解决。
如何抉择异样类型
能够依据下图来抉择是捕捉异样,申明异样还是抛出异样
常见异样解决形式
间接抛出异样
通常,应该捕捉那些晓得如何解决的异样,将不晓得如何解决的异样持续传递上来。传递异样能够在办法签名处应用 throws 关键字申明可能会抛出的异样。
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();
}
封装异样再抛出
有时咱们会从 catch 中抛出一个异样,目标是为了扭转异样的类型。多用于在多系统集成时,当某个子系统故障,异样类型可能有多种,能够用对立的异样类型向外裸露,不需裸露太多外部异样细节。
private static void readFile(String filePath) throws MyException {
try {
// code
} catch (IOException e) {
MyException ex = new MyException(“read file failed.”);
ex.initCause(e);
throw ex;
}
}
捕捉异样
在一个 try-catch 语句块中能够捕捉多个异样类型,并对不同类型的异样做出不同的解决
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
同一个 catch 也能够捕捉多种类型异样,用 | 隔开
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
自定义异样
习惯上,定义一个异样类应蕴含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 办法会打印这些详细信息,调试时很有用)
public class MyException extends Exception {
public MyException(){}
public MyException(String msg){
super(msg);
}
// …
}
try-catch-finally
当办法中产生异样,异样处之后的代码不会再执行,如果之前获取了一些本地资源须要开释,则须要在办法失常完结时和 catch 语句中都调用开释本地资源的代码,显得代码比拟繁琐,finally 语句能够解决这个问题。
调用该办法时,读取文件时若产生异样,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未产生异样,则会跳过 catch 代码块间接进入 finally 代码块。所以无论代码中是否产生异样,fianlly 中的代码都会执行。
若 catch 代码块中蕴含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句批改如下:
catch (IOException e) {
System.out.println(“readFile method catch block.”);
return;
}
调用 readFile 办法,察看当 catch 子句中调用 return 语句时,finally 子句是否执行
private static void readFile(String filePath) throws MyException {
File file = new File(filePath);
String result;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
} catch (IOException e) {
System.out.println(“readFile method catch block.”);
MyException ex = new MyException(“read file failed.”);
ex.initCause(e);
throw ex;
} finally {
System.out.println(“readFile method finally block.”);
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
readFile method catch block.
readFile method finally block.
可见,即便 catch 中蕴含了 return 语句,finally 子句仍然会执行。若 finally 中也蕴含 return 语句,finally 中的 return 会笼罩后面的 return.
try-with-resource
下面例子中,finally 中的 close 办法也可能抛出 IOException, 从而笼罩了原始异样。JAVA 7 提供了更优雅的形式来实现资源的主动开释,主动开释的资源须要是实现了 AutoCloseable 接口的类。
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream(“c:/abc”),”UTF-8″)){
// code
} catch (IOException e){
// handle exception
}
}
try 代码块退出时,会主动调用 scanner.close 办法,和把 scanner.close 办法放在 finally 代码块中不同的是,若 scanner.close 抛出异样,则会被克制,抛出的依然为原始异样。被克制的异样会由 addSusppressed 办法增加到原来的异样,如果想要获取被克制的异样列表,能够调用 getSuppressed 办法来获取。
Java 异样常见面试题
- Error 和 Exception 区别是什么?
Error 类型的谬误通常为虚拟机相干谬误,如零碎解体,内存不足,堆栈溢出等,编译器不会对这类谬误进行检测,JAVA 应用程序也不应答这类谬误进行捕捉,一旦这类谬误产生,通常应用程序会被终止,仅靠应用程序自身无奈复原;
Exception 类的谬误是能够在应用程序中进行捕捉并解决的,通常遇到这种谬误,应答其进行解决,使应用程序能够持续失常运行。 - 运行时异样和个别异样 (受检异样) 区别是什么?
运行时异样包含 RuntimeException 类及其子类,示意 JVM 在运行期间可能呈现的异样。Java 编译器不会查看运行时异样。
受检异样是 Exception 中除 RuntimeException 及其子类之外的异样。Java 编译器会查看受检异样。
RuntimeException 异样和受检异样之间的区别:是否强制要求调用者必须解决此异样,如果强制要求调用者必须进行解决,那么就应用受检异样,否则就抉择非受检异样(RuntimeException)。一般来讲,如果没有非凡的要求,咱们倡议应用 RuntimeException 异样。 - JVM 是如何解决异样的?
在一个办法中如果产生异样,这个办法会创立一个异样对象,并转交给 JVM,该异样对象蕴含异样名称,异样形容以及异样产生时应用程序的状态。创立异样对象并转交给 JVM 的过程称为抛出异样。可能有一系列的办法调用,最终才进入抛出异样的办法,这一系列办法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有能够解决异样的代码,如果有,则调用异样解决代码。当 JVM 发现能够解决异样的代码时,会把产生的异样传递给它。如果 JVM 没有找到能够解决该异样的代码块,JVM 就会将该异样转交给默认的异样处理器(默认处理器为 JVM 的一部分),默认异样处理器打印出异样信息并终止应用程序。 - throw 和 throws 的区别是什么?
Java 中的异样解决除了包含捕捉异样和解决异样之外,还包含申明异样和拋出异样,能够通过 throws 关键字在办法上申明该办法要拋出的异样,或者在办法外部通过 throw 拋出异样对象。
throws 关键字和 throw 关键字在应用上的几点区别如下:
throw 关键字用在办法外部,只能用于抛出一种异样,用来抛出办法或代码块中的异样,受查异样和非受查异样都能够被抛出。
throws 关键字用在办法申明上,能够抛出多个异样,用来标识该办法可能抛出的异样列表。一个办法用 throws 标识了可能抛出的异样列表,调用该办法的办法中必须蕴含可解决异样的代码,否则也要在办法签名中用 throws 关键字申明相应的异样。
- final、finally、finalize 有什么区别?
final 能够润饰类、变量、办法,润饰类示意该类不能被继承、润饰办法示意该办法不能被重写、润饰变量示意该变量是一个常量不能被从新赋值。
finally 个别作用在 try-catch 代码块中,在解决异样的时候,通常咱们将肯定要执行的代码办法 finally 代码块中,示意不论是否出现异常,该代码块都会执行,个别用来寄存一些敞开资源的代码。
finalize 是一个办法,属于 Object 类的一个办法,而 Object 类是所有类的父类,Java 中容许应用 finalize()办法在垃圾收集器将对象从内存中革除进来之前做必要的清理工作。
- NoClassDefFoundError 和 ClassNotFoundException 区别?
NoClassDefFoundError 是一个 Error 类型的异样,是由 JVM 引起的,不应该尝试捕捉这个异样。
引起该异样的起因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作产生在运行期间,即编译时该类存在,然而在运行时却找不到了,可能是变异后被删除了等起因导致;
ClassNotFoundException 是一个受查异样,须要显式地应用 try-catch 对其进行捕捉和解决,或在办法签名中用 throws 关键字进行申明。当应用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动静加载类到内存的时候,通过传入的类门路参数没有找到该类,就会抛出该异样;另一种抛出该异样的可能起因是某个类曾经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。 - try-catch-finally 中哪个局部能够省略?
答:catch 能够省略
起因
更为严格的说法其实是:try 只适宜解决运行时异样,try+catch 适宜解决运行时异样 + 一般异样。也就是说,如果你只用 try 去解决一般异样却不加以 catch 解决,编译是通不过的,因为编译器硬性规定,一般异样如果抉择捕捉,则必须用 catch 显示申明以便进一步解决。而运行时异样在编译时没有如此规定,所以 catch 能够省略,你加上 catch 编译器也感觉无可非议。
实践上,编译器看任何代码都不悦目,都感觉可能有潜在的问题,所以你即便对所有代码加上 try,代码在运行期时也只不过是在失常运行的根底上加一层皮。然而你一旦对一段代码加上 try,就等于显示地承诺编译器,对这段代码可能抛出的异样进行捕捉而非向上抛出解决。如果是一般异样,编译器要求必须用 catch 捕捉以便进一步解决;如果运行时异样,捕捉而后抛弃并且 +finally 开头解决,或者加上 catch 捕捉以便进一步解决。
至于加上 finally,则是在不论有没捕捉异样,都要进行的“开头”解决。 - try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:会执行,在 return 前执行。
留神:在 finally 中扭转返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行结束之后再向调用者返回其值,而后如果在 finally 中批改了返回值,就会返回批改后的值。显然,在 finally 中返回或者批改返回值会对程序造成很大的困扰,C# 中间接用编译谬误的形式来阻止程序员干这种肮脏的事件,Java 中也能够通过晋升编译器的语法查看级别来产生正告或谬误。
代码示例 1:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是 return a 而是 return 30;这个返回门路就造成了
* 然而呢,它发现前面还有 finally,所以继续执行 finally 的内容,a=40
* 再次回到以前的门路, 持续走 return 30,造成返回门路之后,这里的 a 就不是 a 变量了,而是常量 30
*/
} finally {
a = 40;
}
return a;
}
执行后果:30
代码示例 2:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
// 如果这样,就又从新造成了一条返回门路,因为只能通过 1 个 return 返回,所以这里间接返回 40
return a;
}
}
执行后果:40
- 类 ExampleA 继承 Exception,类 ExampleB 继承 ExampleA。
有如下代码片断:
try {
throw new ExampleB(“b”)
} catch(ExampleA e){
System.out.println(“ExampleA”);
} catch(Exception e){
System.out.println(“Exception”);
}
请问执行此段代码的输入是什么?
答:
输入:ExampleA。(依据里氏代换准则 [能应用父类型的中央肯定能应用子类型],抓取 ExampleA 类型异样的 catch 块可能抓住 try 块中抛出的 ExampleB 类型的异样)
面试题 – 说出上面代码的运行后果。(此题的出处是《Java 编程思维》一书)
class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
class Human {
public static void main(String[] args)
throws Exception {
try {
try {
throw new Sneeze();
} catch (Annoyance a) {
System.out.println(“Caught Annoyance”);
throw a;
}
} catch (Sneeze s) {
System.out.println(“Caught Sneeze”);
return ;
} finally {
System.out.println(“Hello World!”);
}
}
}
后果
Caught Annoyance
Caught Sneeze
Hello World!
- 常见的 RuntimeException 有哪些?
ClassCastException(类转换异样)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异样,操作数组时类型不统一)
还有 IO 操作的 BufferOverflowException 异样
- Java 常见异样有哪些
java.lang.IllegalAccessError:守法拜访谬误。当一个利用试图拜访、批改某个类的域(Field)或者调用其办法,然而又违反域或办法的可见性申明,则抛出该异样。
java.lang.InstantiationError:实例化谬误。当一个利用试图通过 Java 的 new 操作符结构一个抽象类或者接口时抛出该异样.
java.lang.OutOfMemoryError:内存不足谬误。当可用内存不足以让 Java 虚拟机调配给一个对象时抛出该谬误。
java.lang.StackOverflowError:堆栈溢出谬误。当一个利用递归调用的档次太深而导致堆栈溢出或者陷入死循环时抛出该谬误。
java.lang.ClassCastException:类造型异样。假如有类 A 和 B(A 不是 B 的父类或子类),O 是 A 的实例,那么当强制将 O 结构为类 B 的实例时抛出该异样。该异样常常被称为强制类型转换异样。
java.lang.ClassNotFoundException:找不到类异样。当利用试图依据字符串模式的类名结构类,而在遍历 CLASSPAH 之后找不到对应名称的 class 文件时,抛出该异样。
java.lang.ArithmeticException:算术条件异样。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException:数组索引越界异样。当对数组的索引值为正数或大于等于数组大小时抛出。
java.lang.IndexOutOfBoundsException:索引越界异样。当拜访某个序列的索引值小于 0 或大于等于序列大小时,抛出该异样。
java.lang.InstantiationException:实例化异样。当试图通过 newInstance() 办法创立某个类的实例,而该类是一个抽象类或接口时,抛出该异样。
java.lang.NoSuchFieldException:属性不存在异样。当拜访某个类的不存在的属性时抛出该异样。
java.lang.NoSuchMethodException:办法不存在异样。当拜访某个类的不存在的办法时抛出该异样。
java.lang.NullPointerException:空指针异样。当利用试图在要求应用对象的中央应用了 null 时,抛出该异样。譬如:调用 null 对象的实例办法、拜访 null 对象的属性、计算 null 对象的长度、应用 throw 语句抛出 null 等等。
java.lang.NumberFormatException:数字格局异样。当试图将一个 String 转换为指定的数字类型,而该字符串确不满足数字类型要求的格局时,抛出该异样。
java.lang.StringIndexOutOfBoundsException:字符串索引越界异样。当应用索引值拜访某个字符串中的字符,而该索引值小于 0 或大于等于序列大小时,抛出该异样。
Java 异样解决最佳实际
在 Java 中解决异样并不是一个简略的事件。不仅仅初学者很难了解,即便一些有教训的开发者也须要破费很多工夫来思考如何解决异样,包含须要解决哪些异样,怎么解决等等。这也是绝大多数开发团队都会制订一些规定来标准进行异样解决的起因。而团队之间的这些标准往往是截然不同的。
本文给出几个被很多团队应用的异样解决最佳实际。
- 在 finally 块中清理资源或者应用 try-with-resource 语句
当应用相似 InputStream 这种须要应用后敞开的资源时,一个常见的谬误就是在 try 块的最初敞开资源。
public void doNotCloseResourceInTry() {
FileInputStream inputStream = null;
try {
File file = new File(“./tmp.txt”);
inputStream = new FileInputStream(file);
// use the inputStream to read a file
// do NOT do this
inputStream.close();
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
问题就是,只有没有异样抛出的时候,这段代码才能够失常工作。try 代码块内代码会失常执行,并且资源能够失常敞开。然而,应用 try 代码块是有起因的,个别调用一个或多个可能抛出异样的办法,而且,你本人也可能会抛出一个异样,这意味着代码可能不会执行到 try 代码块的最初局部。后果就是,你并没有敞开资源。
所以,你应该把清理工作的代码放到 finally 里去,或者应用 try-with-resource 个性。
1.1 应用 finally 代码块
与后面几行 try 代码块不同,finally 代码块总是会被执行。不论 try 代码块胜利执行之后还是你在 catch 代码块中解决完异样后都会执行。因而,你能够确保你清理了所有关上的资源。
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File(“./tmp.txt”);
inputStream = new FileInputStream(file);
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
1.2 Java 7 的 try-with-resource 语法
如果你的资源实现了 AutoCloseable 接口,你能够应用这个语法。大多数的 Java 规范资源都继承了这个接口。当你在 try 子句中关上资源,资源会在 try 代码块执行后或异样解决后主动敞开。
public void automaticallyCloseResource() {
File file = new File(“./tmp.txt”);
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
- 优先明确的异样
你抛出的异样越明确越好,永远记住,你的共事或者几个月之后的你,将会调用你的办法并且解决异样。
因而须要保障提供给他们尽可能多的信息。这样你的 API 更容易被了解。你的办法的调用者可能更好的解决异样并且防止额定的查看。
因而,总是尝试寻找最适宜你的异样事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException。防止抛出一个不明确的异样。
public void doNotDoThis() throws Exception {
…
}
public void doThis() throws NumberFormatException {
…
}
- 对异样进行文档阐明
当在办法上申明抛出异样时,也须要进行文档阐明。目标是为了给调用者提供尽可能多的信息,从而能够更好地防止或解决异样。
在 Javadoc 增加 @throws 申明,并且形容抛出异样的场景。
public void doSomething(String input) throws MyBusinessException {
…
}
- 应用描述性音讯抛出异样
在抛出异样时,须要尽可能准确地形容问题和相干信息,这样无论是打印到日志中还是在监控工具中,都可能更容易被人浏览,从而能够更好地定位具体错误信息、谬误的重大水平等。
但这里并不是说要对错误信息简明扼要,因为原本 Exception 的类名就可能反映谬误的起因,因而只须要用一到两句话形容即可。
如果抛出一个特定的异样,它的类名很可能曾经形容了这种谬误。所以,你不须要提供很多额定的信息。一个很好的例子是 NumberFormatException。当你以谬误的格局提供 String 时,它将被 java.lang.Long 类的构造函数抛出。
try {
new Long(“xyz”);
} catch (NumberFormatException e) {
log.error(e);
}
- 优先捕捉最具体的异样
大多数 IDE 都能够帮忙你实现这个最佳实际。当你尝试首先捕捉较不具体的异样时,它们会报告无法访问的代码块。
但问题在于,只有匹配异样的第一个 catch 块会被执行。因而,如果首先捕捉 IllegalArgumentException,则永远不会达到应该解决更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。
总是优先捕捉最具体的异样类,并将不太具体的 catch 块增加到列表的开端。
你能够在上面的代码片断中看到这样一个 try-catch 语句的例子。第一个 catch 块解决所有 NumberFormatException 异样,第二个解决所有非 NumberFormatException 异样的 IllegalArgumentException 异样。
public void catchMostSpecificExceptionFirst() {
try {
doSomething(“A message”);
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
- 不要捕捉 Throwable 类
Throwable 是所有异样和谬误的超类。你能够在 catch 子句中应用它,然而你永远不应该这样做!
如果在 catch 子句中应用 Throwable,它不仅会捕捉所有异样,也将捕捉所有的谬误。JVM 抛出谬误,指出不应该由利用程序处理的重大问题。典型的例子是 OutOfMemoryError 或者 StackOverflowError。两者都是由利用程序控制之外的状况引起的,无奈解决。
所以,最好不要捕捉 Throwable,除非你确定本人处于一种非凡的状况下可能处理错误。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don’t do this!
}
}
- 不要疏忽异样
很多时候,开发者很有自信不会抛出异样,因而写了一个 catch 块,然而没有做任何解决或者记录日志。
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
但事实是常常会呈现无奈意料的异样,或者无奈确定这里的代码将来是不是会改变 (删除了阻止异样抛出的代码),而此时因为异样被捕捉,使得无奈拿到足够的错误信息来定位问题。
正当的做法是至多要记录异样的信息。
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error(“This should never happen: ” + e);
}
}
- 不要记录并抛出异样
这可能是本文中最常被疏忽的最佳实际。能够发现很多代码甚至类库中都会有捕捉异样、记录日志并再次抛出的逻辑。如下:
try {
new Long(“xyz”);
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
这个解决逻辑看着是正当的。但这常常会给同一个异样输入多条日志。如下:
17:44:28,945 ERROR TestExceptionHandling:65 – java.lang.NumberFormatException: For input string: “xyz”
Exception in thread “main” java.lang.NumberFormatException: For input string: “xyz”
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
如上所示,前面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么能够将异样包装为自定义异样。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException(“A message that describes the error.”, e);
}
}
因而,仅仅当想要解决异样时才去捕捉,否则只须要在办法签名中申明让调用者去解决。
- 包装异样时不要摈弃原始的异样
捕捉规范异样并包装为自定义异样是一个很常见的做法。这样能够增加更为具体的异样信息并可能做针对的异样解决。
在你这样做时,请确保将原始异样设置为起因(注:参考下方代码 NumberFormatException e 中的原始异样 e)。Exception 类提供了非凡的构造函数办法,它承受一个 Throwable 作为参数。否则,你将会失落堆栈跟踪和原始异样的音讯,这将会使剖析导致异样的异样事件变得艰难。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException(“A message that describes the error.”, e);
}
}
- 不要应用异样控制程序的流程
不应该应用异样管制利用的执行流程,例如,本应该应用 if 语句进行条件判断的状况下,你却应用异样解决,这是十分不好的习惯,会重大影响利用的性能。 - 应用规范异样
如果应用内建的异样能够解决问题,就不要定义本人的异样。Java API 提供了上百种针对不同状况的异样类型,在开发中首先尽可能应用 Java API 提供的异样,如果规范的异样不能满足你的要求,这时候创立本人的定制异样。尽可能得应用规范异样有利于新退出的开发者看懂我的项目代码。 - 异样会影响性能
异样解决的性能老本十分高,每个 Java 程序员在开发时都应牢记这句话。创立一个异样十分慢,抛出一个异样又会耗费 1~5ms,当一个异样在利用的多个层级之间传递时,会连累整个利用的性能。
仅在异常情况下应用异样;
在可复原的异常情况下应用异样;
只管应用异样有利于 Java 开发,然而在利用中最好不要捕捉太多的调用栈,因为在很多状况下都不须要打印调用栈就晓得哪里出错了。因而,异样音讯应该提供恰到好处的信息。
- 总结
综上所述,当你抛出或捕捉异样的时候,有很多不同的状况须要思考,而且大部分事件都是为了改善代码的可读性或者 API 的可用性。
异样不仅仅是一个谬误管制机制,也是一个通信媒介。因而,为了和共事更好的单干,一个团队必须要制订出一个最佳实际和规定,只有这样,团队成员能力了解这些通用概念,同时在工作中应用它。
异样解决 - 阿里巴巴 Java 开发手册
【强制】Java 类库中定义的能够通过预查看形式躲避的 RuntimeException 异样不应该通过 catch 的形式来解决,比方:NullPointerException,IndexOutOfBoundsException 等等。阐明:无奈通过预查看的异样除外,比方,在解析字符串模式的数字时,可能存在数字格局谬误,不得不通过 catch NumberFormatException 来实现。正例:if (obj != null) {…} 反例:try {obj.method(); } catch (NullPointerException e) {…}
【强制】异样不要用来做流程管制,条件管制。阐明:异样设计的初衷是解决程序运行中的各种意外状况,且异样的解决效率比条件判断形式要低很多。
【强制】catch 时请分清稳固代码和非稳固代码,稳固代码指的是无论如何不会出错的代码。对于非稳固代码的 catch 尽可能进行辨别异样类型,再做对应的异样解决。阐明:对大段代码进行 try-catch,使程序无奈依据不同的异样做出正确的应激反应,也不利于定位问题,这是一种不负责任的体现。正例:用户注册的场景中,如果用户输出非法字符,或用户名称已存在,或用户输出明码过于简略,在程序上作出分门别类的判断,并提醒给用户。
【强制】捕捉异样是为了解决它,不要捕捉了却什么都不解决而摈弃之,如果不想解决它,请将该异样抛给它的调用者。最外层的业务使用者,必须解决异样,将其转化为用户能够了解的内容。
【强制】有 try 块放到了事务代码中,catch 异样后,如果须要回滚事务,肯定要留神手动回滚事务。
【强制】finally 块必须对资源对象、流对象进行敞开,有异样也要做 try-catch。阐明:如果 JDK7 及以上,能够应用 try-with-resources 形式。
【强制】不要在 finally 块中应用 return。阐明:try 块中的 return 语句执行胜利后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此间接返回,有情抛弃掉 try 块中的返回点。反例:
private int x = 0;
public int checkReturn() {
try {
// x 等于 1,此处不返回
return ++x;
} finally {
// 返回的后果是 2
return ++x;
}
}
【强制】捕捉异样与抛异样,必须是齐全匹配,或者捕捉异样是抛异样的父类。阐明:如果预期对方抛的是绣球,理论接到的是铅球,就会产生意外状况。
【强制】在调用 RPC、二方包、或动静生成类的相干办法时,捕获异样必须应用 Throwable 类来进行拦挡。java 培训阐明:通过反射机制来调用办法,如果找不到办法,抛出 NoSuchMethodException。什么状况会抛出 NoSuchMethodError 呢?二方包在类抵触时,仲裁机制可能导致引入非预期的版本使类的办法签名不匹配,或者在字节码批改框架(比方:ASM)动态创建或批改类时,批改了相应的办法签名。这些状况,即便代码编译期是正确的,但在代码运行期时,会抛出 NoSuchMethodError。
【举荐】办法的返回值能够为 null,不强制返回空集合,或者空对象等,必须增加正文充分说明什么状况下会返回 null 值。阐明:本手册明确避免 NPE 是调用者的责任。即便被调用办法返回空集合或者空对象,对调用者来说,也并非居安思危,必须思考到近程调用失败、序列化失败、运行时异样等场景返回 null 的状况。
【举荐】避免 NPE,是程序员的根本涵养,留神 NPE 产生的场景:
1)返回类型为根本数据类型,return 包装数据类型的对象时,主动拆箱有可能产生 NPE。反例:public int f() { return Integer 对象},如果为 null,主动解箱抛 NPE。
2)数据库的查问后果可能为 null。
3)汇合里的元素即便 isNotEmpty,取出的数据元素也可能为 null。
4)近程调用返回对象时,一律要求进行空指针判断,避免 NPE。5)对于 Session 中获取的数据,倡议进行 NPE 查看,防止空指针。
6)级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
正例:应用 JDK8 的 Optional 类来避免 NPE 问题。
【举荐】定义时辨别 unchecked / checked 异样,防止间接抛出 new RuntimeException(),更不容许抛出 Exception 或者 Throwable,应应用有业务含意的自定义异样。举荐业界已定义过的自定义异样,如:DAOException / ServiceException 等。
【参考】对于公司外的 http/api 凋谢接口必须应用“错误码”;而利用外部举荐异样抛出;跨利用间 RPC 调用优先思考应用 Result 形式,封装 isSuccess()办法、“错误码”、“谬误简短信息”。阐明:对于 RPC 办法返回形式应用 Result 形式的理由:
1)应用抛异样返回形式,调用方如果没有捕捉到就会产生运行时谬误。
2)如果不加栈信息,只是 new 自定义异样,退出本人的了解的 error message,对于调用端解决问题的帮忙不会太多。如果加了栈信息,在频繁调用出错的状况下,数据序列化和传输的性能损耗也是问题。