1. 重抛异常
重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略,此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息,要想更新这个信息,可以调用fillInStackTrace()方法,将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,
调用fillInStackTrace()的那一行就成了异常的新发生地。另外,有可能在捕获异常之后抛出另一种异常,那么做的话,得到的效果类似于fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息。
/**
1. @Author ZhangGJ
2. @Date 2020/11/25 07:51
*/
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch (Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception) e.fillInStackTrace();
}
}
public static void main(String[] args) {
try {
g();
} catch (Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch (Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
}
2. 异常链
常常会想要在捕获一个异常后重新抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。
Throwable的子类在构造器中都可以接受一个cause对象作为参数,这个cause就是用来表示原始异常,这样通过把原始异常传递个新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
Throwable的子类中,只有三种基本的异常提供了带cause参数的构造器,Error,Exception,RuntimeException,如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。
class DynamicFieldsException extends Exception {
}
/**
* @Author ZhangGJ
* @Date 2020/11/25 08:20
*/
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 result = new StringBuilder();
for (Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.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 i = 0; i < fields.length; i++) {
tmp[i] = fields[i];
}
for (int i = fields.length; i < tmp.length; i++) {
tmp[i] = 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 {
/**
* Get old value
*/
result = getField(id);
} 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.print(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.print(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.print("df: " + df);
System.out.print("df.getField(\"d\") : " + df.getField("d"));
/**
* Exception
*/
Object field = df.setField("d", null);
} catch (NoSuchFieldException e) {
e.printStackTrace(System.out);
} catch (DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
3.异常丢失
Java的异常实现也有缺点,异常有可能被忽略。
class VeryImportantException extends Exception {
@Override
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
@Override
public String toString() {
return "A trivial exception";
}
}
/**
* @Author ZhangGJ
* @Date 2020/11/26 07:10
*/
public class LostMessage {
public void f() throws VeryImportantException {
throw new VeryImportantException();
}
public void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally {
lm.dispose();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
4.异常的限制
-
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常;
-
异常限制对构造器不起作用,派生类构造器的异常说明必须包含基类构造器的异常说明,派生类构造器不能捕获基类构造器抛出的异常;
-
派生类方法可以不抛出任何异常,即使它是基类所定义的异常;
-
一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了,而是变小了,这恰好和类接口在继承时的情形相反。
class BaseballException extends Exception {
}
class Foul extends BaseballException {
}
class Strike extends BaseballException {
}
abstract class Inning {
public Inning() throws BaseballException {
}
public void event() throws BaseballException {
// Doesn’t actually have to throw anything
}
public abstract void atBat() throws Strike, Foul;
public void walk() {
} // Throws no checked exceptions
}
class StormException extends Exception {
}
class RainedOut extends StormException {
}
class PopFoul extends Foul {
}
interface Storm {
public void event() throws RainedOut;
public void rainHard() throws RainedOut;
}
/**
* @Author ZhangGJ
* @Date 2020/11/26 07:20
*/
public class StormyInning extends Inning implements Storm {
// OK to add new exceptions for constructors, but you
// must deal with the base constructor exceptions:
public StormyInning() throws RainedOut, BaseballException {
}
public StormyInning(String s) throws Foul, BaseballException {
}
// Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//! public void event() throws RainedOut {}
// If the method doesn’t already exist in the
// base class, the exception is OK:
@Override
public void rainHard() throws RainedOut {
}
// You can choose to not throw any exceptions,
// even if the base version does:
@Override
public void event() {
}
// Overridden methods can throw inherited exceptions:
@Override
public void atBat() throws PopFoul {
}
public static void main(String[] args) {
try {
StormyInning si = new StormyInning();
si.atBat();
} catch (PopFoul e) {
System.out.println("Pop foul");
} catch (RainedOut e) {
System.out.println("Rained out");
} catch (BaseballException e) {
System.out.println("Generic baseball exception");
}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// You must catch the exceptions from the
// base-class version of the method:
} catch (Strike e) {
System.out.println("Strike");
} catch (Foul e) {
System.out.println("Foul");
} catch (RainedOut e) {
System.out.println("Rained out");
} catch (BaseballException e) {
System.out.println("Generic baseball exception");
}
}
}