异常体系Java的基本理念是“结构不佳的代码不能运行”。
-
Throwable(任何可以作为异常被抛出的类)
-
Exception 受检异常(在编译时被强制检查的异常)
-
RuntimeException 非受检异常(运⾏时异常,代表⼀种预料之外的异常,因此不需要声明)
- 几个常见的RuntimeException:IndexOutOfBoundsException、ClassCastException、NullPointerException、NumberFormatException
-
Error 编译时和系统错误,用于虚拟机报告系统错误
-
-
RuntimeException是Exception的子类;
-
RuntimeException标注的异常可以不需要进行强制性处理,而Exception异常必须强制性处理;
-
只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:
-
无法预料的错误。比如从你控制范围之外传递进来的null引用
-
作为程序员,应该在代码中进行检查的错误。(比如对于ArrayindexOutOfBoundsException,就得注意一下数组的大小了。)在一个地方发生的异常,常常会在另一个地方导致错误。
-
自定义异常类分为继承Exception和RuntimeException两种情况。
-
Exception:main方法要处理异常,此异常处理方式为抛出异常
-
RuntimeException:main不用处理异常
public class Main {
public static void main(String[] args) {
Food.eat(11);
}
}
class BombException extends RuntimeException{
public BombException(String msg){
super(msg);
}
}
class Food{
public static void eat(int num) throws BombException{
if(num > 10){
throw new BombException("eat too much");
}else{
System.out.println("eating");
}
}
}
总结:通过这一个自定义异常demo可以更清楚区分出:Exception和RuntimeException的区别。
异常匹配抛出异常的时候,异常处理系统会按照代码的书写顺序找出「最近」的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类对象也可以匹配其基类的处理程序
- demo : Catching exception hierarchies
// Catching exception hierarchies(等级制度).
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
public static void main(String[] args) {
// Catch the exact type:
try {
throw new Sneeze();
} catch (Sneeze s) {
System.out.println("Caught Sneeze");
} catch (Annoyance a) {
System.out.println("Caught Annoyance");
}
// Caught the base type:
try {
throw new Sneeze();
} catch (Annoyance a) {
System.out.println("Caught Annoyance");
}
}
}
//:~
- demo : 把捕获基类的catch子句放在最前面
try {
throw new Sneeze();
} catch (Annoyance a) {
System.out.println("Caught Annoyance");
//! Exception 'com.company.exception.Sneeze' has already been caught
} catch (Sneeze s) {
System.out.println("Caught Sneeze");
}
此时编译器会发现Sneeze的catch子句永远也执行不到,因此它会向你报告错误。
catch的级联与合并,父子级异常直接抓父亲, 处理方式一样的异常用|
- demo
try {
throwCheckedException();
} catch (NullPointException | IllegalAccessException e) {
//打印完整异常信息
e.printStackTrace();
}
捕获所有异常
栈轨迹
e.printStackTrace() (排查问题最重要的信息,没有之⼀)
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问。
- demo : Programmatic access to stack information
// Programmatic access to stack information
public class WhoCalled {
static void f() {
// Generate an exception to fill in the stack trace
try{
throw new Exception();
}catch(Exception e){
for (StackTraceElement ste : e.getStackTrace()){
System.out.println("ste.getMethodName() = " + ste.getMethodName());
}
e.printStackTrace();
}
}
static void g(){ f(); }
static void h(){ g(); }
public static void main(String[] args) {
f();
System.out.println("--------------------");
g();
System.out.println("--------------------");
h();
}
}
//:~
异常链
捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。JDK1.4之后,所有Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。异常向上抛出时,每多包一层,就多一个Caused by。
在Throwable的子类中,只有三种基本类型的异常类提供了带cause参数的构造器。他们是Error、Exceptio以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法,而不是构造器。
- demo:使用initCause()方法把异常链接起来
// A Class that dynamically adds fields to itself.
// Demonstrates exception chaining.(展示异常链)
package com.company.exception;
class DynamicFieldsException extends Exception {
}
public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for (int i = 0; i < initialSize; i++) {
fields[i] = new Object[]{null, null};
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Object[] obj : fields) {
builder.append(obj[0]);
builder.append(": ");
builder.append(obj[1]);
builder.append("n");
}
return builder.toString();
}
private int hasField(String id) {
for (int i = 0; i < fields.length; i++) {
if (id.equals(fields[i][0])) {
return i;
}
}
return -1;
}
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if (fieldNum == -1) {
throw new NoSuchFieldException();
}
return fieldNum;
}
private int makeField(String id) {
for (int i = 0; i < fields.length; i++)
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// No empty fields.Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for (int j = 0; j < fields.length; j++) {
tmp[j] = fields[j];
}
for (int k = fields.length; k < tmp.length; k++) {
tmp[k] = new Object[]{null, null};
}
fields = tmp;
// Recursive call with expanded fields:
return makeField(id);
}
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
public Object setField(String id, Object value) throws DynamicFieldsException {
if (value == null) {
// Most exceptions don't have a "cause" constructor.
// In these cases you must use initCause(),
// available in all Throwable subclasses.
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;
}
int fieldNumber = hasField(id);
if (fieldNumber == -1) {
fieldNumber = makeField(id);
}
Object result = null;
try {
result = getField(id); // Get old value
} catch (NoSuchFieldException e) {
// Use constructor that takes "cause"
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println(df);
System.out.println("df.getField("d") = " + df.getField("d"));
Object field = df.setField("d", null); // Exception
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
//:~
- demo : Exception类型的方法
// Demonstrating the Exception Methods.
public class ExceptionMethods {
public static void main(String[] args) {
try {
throw new Exception("My Exception");
} catch (Exception e) {
System.out.println("Caught Exception");
System.out.println("e.getMessage() = " + e.getMessage());
System.out.println("e.getLocalizedMessage() = " + e.getLocalizedMessage());
System.out.println("toString() = " + e);
System.out.println("printStackTrace():");
e.printStackTrace(System.out);
}
}
}
//:~
使用finally进行清理
finally用来做什么
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。无论异常是否被抛出,finally子句总能被执行。(JDK8:try-with-resources)
甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally语句
- demo : AlwayFinally
package com.company.exception;
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
System.out.println("Entering first try block");
try {
System.out.println("Entering second try block");
try {
throw new FourException();
} finally {
System.out.println("finally in 2nd try block");
}
} catch (FourException e) {
System.out.println("Caught FourException in 1st try block");
} finally {
System.out.println("finally in 1st try block");
}
}
}
当涉及break和continue语句的时候,finally子句也会得到执行。
在return中使用finally因为finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍会执行。
- demo : MultipleReturns
package com.company.exception;
public class MultipleReturns {
public static void f(int i){
System.out.println("Initialization that requires cleanup");
try{
System.out.println("Point 1");
if(i == 1) return;
System.out.println("Point 2");
if(i == 2) return;
System.out.println("Point 3");
if(i == 3) return;
System.out.println("End");
}finally {
System.out.println("Performing cleanup");
}
}
public static void main(String[] args) {
for (int i = 1; i < 4; i++) {
f(i);
}
}
}
异常的抛出原则
-
能⽤if/else处理的,不要使⽤异常
-
尽早抛出异常
-
异常要准确、带有详细信息
-
抛出异常也⽐悄悄地执⾏错误的逻辑强的多
-
本⽅法是否有责任处理这个异常?
- 不要处理不归⾃⼰管的异常
-
本⽅法是否有能⼒处理这个异常?
- 如果⾃⼰⽆法处理,就抛出
-
如⾮万分必要,不要忽略异常
- 使用 -ea 开启断言
public class Main {
public static void main(String[] args) {
int x = 10;
assert x == 100 : "x的内容不是100";
System.out.println(x);
}
}
//:~



