通过异常处理错误

 

1、概念

2、基本异常

异常处理程序将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
在没有其它办法的情况下,异常允许我们强制程序停止运行,并告诉我们出现了什么问题。理想状态下,还可以强制程序处理问题,并返回到稳定状态的。

 

异常参数

关键字throw

3、捕获异常

监控区域是一个可能产生异常的代码,并且后面跟着处理这些异常的代码。

try块:

如果在方法内部抛出了异常,那么这个方法就此结束。如果不希望这个方法结束,那么可以在方法内设置一个特殊的块来捕获异常,即try块。为什么叫try呢,因为在这个块里“尝试”各种可能产生异常的方法进行调用,所以是try。

异常处理程序catch:

try {
    // Code that might generate exceptions
} catch(Type1 id1)|{
    // Handle exceptions of Type1
} catch(Type2 id2) {
    // Handle exceptions of Type2
} catch(Type3 id3) {
    // Handle exceptions of Type3
}

终止与恢复:

 


4、创建自定义异常

package exception;
class SimpleException extends Exception {}
public class InheritingExceptions {
    public void f() throws SimpleException {
         System.out.println("Throw SimpleException from f()");
         throw new SimpleException();
    }
    public static void main(String[] args) {
         InheritingExceptions sed = new InheritingExceptions();
         try {
           sed.f();
         } catch(SimpleException e) {
           System.out.println("Caught it!");
         }
    }
} 

输出:

对异常来说,最重要的部分是类名。

class MyException extends Exception {
public MyException() {}
public MyException(String msg) { super(msg); }
}
public class FullConstructors {
public static void f() throws MyException {
  System.out.println("Throwing MyException from f()");
  throw new MyException();
}
public static void g() throws MyException {
  System.out.println("Throwing MyException from g()");
  throw new MyException("Originated in g()");
}
public static void main(String[] args) {
  try {
    f();
  } catch(MyException e) {
    e.printStackTrace(System.out);//如果该方法不带参,那么信息将被输出到标准错误流
  }
  try {
    g();
  } catch(MyException e) {
    e.printStackTrace(System.out);
  }
}
} 
输出:

 

 
异常与记录日志:
 
使用java.util.logging工具将输出记录到日志中。方法如下:
import java.util.logging.*;
import java.io.*;

class LoggingException extends Exception {
  private static Logger logger =
    Logger.getLogger("LoggingException");
  public LoggingException() {
    StringWriter trace = new StringWriter();
    printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
}

public class LoggingExceptions {
  public static void main(String[] args) {
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
  }
}

 输出结果:

 

当然,不能指望每个程序员把记录日志的程序的基础设施都构建在异常里,所以更常见的情形是需要捕获和记录他人编写的异常,因此需要在异常处理程序中生成日志消息,如下:

import java.util.logging.*;
import java.io.*;

public class LoggingExceptions2 {
  private static Logger logger =
    Logger.getLogger("LoggingExceptions2");
  static void logException(Exception e) {
    StringWriter trace = new StringWriter();
    e.printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
  public static void main(String[] args) {
    try {
      throw new NullPointerException();
    } catch(NullPointerException e) {
      logException(e);
    }
  }
}

输出结果:

我们甚至可以进一步定义异常,比如加入额外的构造器和成员,然而一般来说并不会用上这些功能。
 
class MyException2 extends Exception {
  private int x;
  public MyException2() {}
  public MyException2(String msg) { super(msg); }
  public MyException2(String msg, int x) {
    super(msg);
    this.x = x;
  }
  public int val() { return x; }
  public String getMessage() {
    return "Detail Message: "+ x + " "+ super.getMessage();
  }
}

public class ExtraFeatures {
  public static void f() throws MyException2 {
    System.out.println("Throwing MyException2 from f()");
    throw new MyException2();
  }
  public static void g() throws MyException2 {
    System.out.println("Throwing MyException2 from g()");
    throw new MyException2("Originated in g()");
  }
  public static void h() throws MyException2 {
    System.out.println("Throwing MyException2 from h()");
    throw new MyException2("Originated in h()", 47);
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
    }
    try {
      g();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
    }
    try {
      h();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
      System.out.println("e.val() = " + e.val());
    }
  }
}

输出结果:

5、异常说明

异常说明使用了关键字 throws,后面接一个潜在的异常类型列表。
void f() throws TooBig, TooSmall, DivZero { //...
这种在编译时被强制检查的异常称为被检查的异常。
也可以声明方法将抛出异常,但是实际上却不抛出。这样做可以先为异常占个位置,以后可以抛出这类异常而不用修改已有方法,这种“作弊”方法通常用在你定义抽象基类和接口时,这样派生类或者接口实现就能抛出这些预先声明的异常。
 

6、捕获所有异常

Exception可以调用其从基类继承的方法:
String getMessage( )
String getLocalizedMessage( )

获取详细信息(抛出异常对象所带的参数),或者用本地语言表示的详细信息。

 

String toString( )
返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内。
 
void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
打印Throwable和Throwable的调用栈轨迹。

Throwable fillInStackTrace( )
用于在Throwable对象的内部记录栈帧的当前状态。
 
几个Object类的方法:
getClass() 返回一个表示此对象类型的对象
getName() 查询这个Class对象包含包信息的名称
getSimpleName() 只产生类名称
 
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("getMessage():" + e.getMessage());
            System.out.println("getLocalizedMessage():" + e.getLocalizedMessage());
            System.out.println("toString():" + e);
            System.out.println("printStackTrace():");
            e.printStackTrace(System.out);
        }
    }
}

输出:

栈轨迹:

printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,该方法返回一个由栈轨迹元素所构成的数组,每个元素表示栈中的一帧,元素0也是栈顶元素,是最后调用的方法(Throwable被创建和抛出之处),最后一个元素是栈底,是调用序列的第一个方法调用。
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());
    }
  }
  static void g() { f(); }
  static void h() { g(); }
  public static void main(String[] args) {
    f();
    System.out.println("--------------------------------");
    g();
    System.out.println("--------------------------------");
    h();
  }
}

输出:

重新抛出异常

package exception;

//: exceptions/Rethrowing.java
//Demonstrating fillInStackTrace()

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);
        }
    }
} 

输出结果:

调用fillInStackTrace()的这一行就成为异常的新发生地了。
在异常捕获之后抛出另一种异常,其效果类似于fillInStackTrace()。
 
class OneException extends Exception {
public OneException(String s) { super(s); }
}

class TwoException extends Exception {
public TwoException(String s) { super(s); }
}

public class RethrowNew {
public static void f() throws OneException {
 System.out.println("originating the exception in f()");
 throw new OneException("thrown from f()");
}
public static void main(String[] args) {
 try {
       try {
         f();
       } catch(OneException e) {
         System.out.println(
           "Caught in inner try, e.printStackTrace()");
         e.printStackTrace(System.out);
         throw new TwoException("from inner try");
       }
 } catch(TwoException e) {
       System.out.println(
         "Caught in outer try, e.printStackTrace()");
       e.printStackTrace(System.out);
 }
}
} 

输出结果:

 

异常链

在捕获一个异常后抛出另一个异常,并希望把原始异常的信息保存下来,这被称为异常链。

 

package exception;

//: exceptions/DynamicFields.java
//A Class that dynamically adds fields to itself.
//Demonstrates exception chaining.

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 };
    }

    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 {
            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: " + 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);
        }
    }
} 

 

 

7、Java标准异常

Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误,Exception是可以被抛出的基本类型,包括Java类库,用户方法以及运行时故障都可以抛出此异常。
Error一般不用自己关心,来讲Exception:

特例RuntimeException

比如nullPointerException,空指针异常。

运行时产生的异常,不需要在异常说明中声明方法将抛出RuntimeException类型的异常。它们被称为“不受检查的异常”。这种异常属于错误,会被自动捕获,而不用程序员自己写代码捕获。

package exception;

public class NeverCaught {
    static void f() {
        throw new RuntimeException("From f()");
    }

    static void g() {
        f();
    }

    public static void main(String[] args) {
        g();
    }
}

如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。
 

 

8、使用finally进行清理

在异常处理程序后面加上finamlly子句,可保证无论try块里的异常是否抛出,都能执行。(通常适用于内存回收之外的情况)
证明finally语句总能执行:
class ThreeException extends Exception {
}

public class FinallyWorks {
    static int count = 0;

    public static void main(String[] args) {
        while (true) {
            try {
                // Post-increment is zero first time:
                if (count++ == 0)
                    throw new ThreeException();
                System.out.println("No exception");
            } catch (ThreeException e) {
                System.out.println("ThreeException");
            } finally {
                System.out.println("In finally clause");
                if (count == 2)
                    break; // out of "while"
            }
        }
    }
}

甚至当当前异常1还没捕获的情况下,又抛出了个异常2,那么这个异常2的finally也会先执行,再到捕获异常1的程序块中。
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子句也会得到执行。
如果把finally子句和带标签的break以及continue配合使用,在java里没必要使用goto语句了。
有return语句时,finally依旧会执行
package 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");
            return;
        } finally {
            System.out.println("Performing cleanup");
        }
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 4; i++)
            f(i);
    }
}

异常丢失
在一个异常还没得到处理的情况下,应该尽量避免抛出另一个异常。
1、使用finally可能导致一个异常还没处理,在接下来的finally字句中又抛出了一个异常,那么前一个异常就会丢失,外面的catch块捕捉到的就是finally抛出的异常而未察觉到最开始抛出的异常。
2、一种更简单的丢失异常的方式是在finally语句中直接return,这就更别说到catch块匹配异常了。
应该避免以上两种编程错误。
1、
class VeryImportantException extends Exception {
    public String toString() {
        return "A very important exception!";
    }
}

class HoHumException extends Exception {
    public String toString() {
        return "A trivial exception";
    }
}

public class LostMessage {
    void f() throws VeryImportantException {
        throw new VeryImportantException();
    }

    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);
        }
    }
}
2、
public class ExceptionSilencer {
  public static void main(String[] args) {
    try {
      throw new RuntimeException();
    } finally {
      // Using 'return' inside the finally block
      // will silence any thrown exception.
      return;
    }
  }
}

 

9、异常的限制

当覆盖 方法时,只能抛出在基类方法的异常说明里列出的那些异常。这个限制意味着,当基类代码运用到派生类时,依旧有用。

package exception;

//: exceptions/StormyInning.java
//Overridden methods may throw only the exceptions
//specified in their base-class versions, or exceptions
//derived from the base-class exceptions.

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;
}

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:
    public void rainHard() throws RainedOut {
    }

    // You can choose to not throw any exceptions,
    // even if the base version does:
    public void event() {
    }

    // Overridden methods can throw inherited exceptions:
    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");
        }
    }
} /// :~

当处理派生类对象时,编译器只会强制要求捕获派生类该方法产生的异常。如果向上转型为基类,编译器会要求捕获基类方法产生的异常。很智能的。
异常说明本身并不属于方法类型的范畴中,因此不参与重载的判断。

基于特定方法的“异常说明的接口”不是变大了而是变小了,小于等于基类异常说明表——这恰好和类接口在继承时的情形相反。

 

10、构造器

 


11、异常的匹配

package exception;

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");
        }
        // Catch the base type:
        try {
            throw new Sneeze();
        } catch (Annoyance a) {
            System.out.println("Caught Annoyance");
        }
    }
}

输出:

 

posted @ 2018-05-20 19:30  邓不利多  阅读(230)  评论(0编辑  收藏  举报