Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions
The ideal time to catch an error is at compile time, before you even try to run the program. However, not all errors can be detected at compile time.
To create a robust system, each component must be robust.
By providing a consistent error-reporting model using exceptions, Java allows components to reliably communicate problems to client code.
Concepts
C and other earlier languages often had multiple error-handling schemes, and these were generally established by convention and not as part of the programming language. Typically you returned a special value or set a flag, and the recipient was supposed to look at the value of the flag and determine that something was amiss.
This approach to handling errors was a major limitation to creating large, robust, maintainable programs.
The solution is to take the casual nature out of error handling and to enforce formality.
At the point where the problem occurs, you might not know what to do with it, but you do know that you can't just continue on merrily; you must stop, and somebody, somewhere, must figure out what to do. But you don't have enough information in the current context to fix the problem. So you hand the problem out to a higher context where someone is qualified to make the proper decision.
The other rather significant benefit of exceptions is that they tend to reduce the complexity of error-handling code.
Without exceptions, you must check for a particular error and deal with it at multiple places in your program
Basic exceptions
It's important to distinguish an exceptional condition from a normal problem, in which you have enough information in the current context to somehow cope with the difficulty.
When you throw an exception, several things happen:
1. the exception object is created in the same way that any Java Object is created: on the heap, with new.
2. the current path of execution is stopped and the reference for the exception object is ejected from the current context
3. At this point the exception-handling mechanism takes over and begins to look for an appropriate place to continue executing the problem. This appropriate is the exception handler.
Catching an exception
a guarded region: a section of code that might produce exceptions and is followed by the code to handle those exceptions.
The try block
try {
// Code that might generate exceptions
}
WIth exception handling, you put everything in a try block and capture all the exceptions in one place. This means your code is much easier to write and read because the goal of the code is not confused with the error checking.
Exception handlers
Exception handlers immediately follow the try block and are denoted by the keyword catch.
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Typen idn) {
// Handle exceptions of Typen
}
Each catch clause (exception handler) is like a little method that takes one and only one argument of a particular type.The identifier (id1, id2, and so on) can be used inside the handler, just like a method argument. Sometimes you never use the identifier because the type of the exception gives you enough information to deal with the exception, but the identifier must still be there.
If an exception is thrown, the exception-handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is considered handled.The search for handlers stops once the catch clause is finished.
Note that within the try block, a number of different method calls might generate the same exception, but you need only one handler.
Termination vs. resumption
If you want resumption-like behavior in Java, don’t throw an exception when you encounter an error. Instead, call a method that fixes the problem. Alternatively, place your try block inside a while loop that keeps reentering the try block until the result is satisfactory.
So although resumption sounds attractive at first, it isn’t quite so useful in practice.The dominant reason is probably the coupling that results: A resumptive handler would need to be aware of where the exception is thrown, and contain non-generic code specific to the throwing location. This makes the code difficult to write and maintain, especially for large systems where the exception can be generated from many points.
Creating your own exceptions
The most trivial way to create a new type of exception is just to let the compiler create the default constructor for you, so it requires almost no code at all
class SimpleException extends Exception {}
in this case you don't get a SimpleException(String) constructor, but in practice that isn't used much (??)
The most important thing about an exception is the class name, so most of the time an exception like the one shown here is satisfactory.
System.err is usually a better place to send error information than System.out, which may be redirected. If you send output to System.err, it will not be redirected along with System.out so the user is more liked to notice it.
class MyException extends Exception {
public MyException() {}
public MyException(String msg) { super(msg); }
}
e.printStackTrace(System.out);
e.printStackTrace(); //自动输出到System.err
Exceptions and logging
java.util.logging
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());
}
}
The static Logger.getLogger() method creates a Logger object associated with the String argument (usually the same of the package and class that the errors are about) which sends its output to System.err. The easiest way to write to a Logger is just to call the method associated with the level of logging message; here, severe( ) is used.
Although the approach used by LoggingException is very convenient because it builds all the logging infrastructure into the exception itself, and thus it works automatically without client programmer intervention, it's more common that you will be catching and logging someone else's exception, so you must generate the log message in the exception handler.
public class LoggingExceptions2 {
private static Logger logger =
Logger.getLogger("LoggingExceptions2");
static void logException(Exception e) { // static 方法
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); // 调用
}
}
}
The process of creating your own exceptions can be taken further.
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();
}
}
// 输出例子:
MyException2: Detail Message: 0 Originated in g()
at ExtraFeatures.g(ExtraFeatures.java:26)
at ExtraFeatures.main(ExtraFeatures.java:39)
getMessage( ) is something like toString( ) for exception classes.
Keep in mind, however, that all this dressing-up might be lost on the client programmers using your packages, since they might simply look for the exception to be thrown and nothing more. (That’s the way most of the Java library exceptions are used.)
The exception specification
void f() throws TooBig, TooSmall, DivZero { //...
However, if you say
void f() { //...
it means that no exceptions are thrown from the method (except for the exceptions inherited from RuntimeException, which can be thrown anywhere without exception specifications—these will be described later).
There is one place you can lie: You can claim to throw an exception that you really don’t.
This has the beneficial effect of being a placeholder for that exception, so you can actually start throwing the exception later without requiring changes to existing code. It’s also important for creating abstract base classes and interfaces whose derived classes or implementations may need to throw exceptions.
Exceptions that are checked and enforced at compile time are called checked exceptions.
Catching any exception
there are other types of base exceptions, but Exception is the base that’s pertinent to virtually all programming activities).
catch(Exception e) {
// ...
}
if you use it you’ll want to put it at the end of your list of handlers to avoid preempting any exception handlers that might otherwise follow it.
The stack trace
The information provided by printStackTrace( ) can also be accessed directly using getStackTrace( ). This method returns an array of stack trace elements, each representing one stack frame. Element zero is the top of the stack, and is the last method invocation in the sequence
Rethrowing an exception
catch (Exception e) {
// ....
throw e;
}
If you simply rethrow the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception’s origin, not the place where you rethrow it. If you want to install new stack trace information, you can do so by calling fillInStackTrace( ), which returns a Throwable object that it creates by stuffing the current stack information into the old exception object.
It’s also possible to rethrow a different exception from the one you caught. If you do this, you get a similar effect as when you use fillInStackTrace( )
Exception chaining
Often you want to catch one exception and throw another, but still keep the information about the originating exception—this is called exception chaining.
all Throwable subclasses have the option to take a cause object in their constructor. The cause is intended to be the originating exception, and by passing it in you maintain the stack trace back to its origin, even though you’re creating and throwing a new exception.
It’s interesting to note that the only Throwable subclasses that provide the cause argument in the constructor are the three fundamental exception classes Error (used by the JVM to report system errors), Exception, and RuntimeException. If you want to chain any other exception types, you do it through the initCause( ) method rather than the constructor.
Standard Java exceptions
The Java class Throwable describes anything that can be thrown as an exception.
There are two general types of Throwable objects:
1. Error: represents compile-time and system errors that you don't worry about catching
2. Exception: the basic type that can be thrown from any of the standard Java library class methods and from your methods and runtime accidents.
There isn't anything special between one exception and the next except for the name.
The basic idea is that the name of the exception represents the problem that occurred, and the exception name is intended to be relatively selfexplanatory.
The exceptions are not all defined in java.lang; some are created to support other libraries such as util, net, and io. For eaxample, all I/O exceptions are inherited from java.io.IOException.
Special case: RuntimeException
if( t == null)
throw new NullPointerException();
类似t == null 的检查,在实际项目中是否有必要都显式检查?(思考)
RuntimeException and any types inherited from RuntimeException are uncheced exceptions
If you were forced to check for RuntimeExceptions, your code could get too messy.
A RuntimeException is a special case, since the compiler doesn't require an exception specification for it. The output is reported to System.err.
If a RuntimeException gets all the way out to main() without being caught, printStackTrace() is called for that exception as the program exits.
A RuntimeException represents a programming error:
1. An error you cannot anticipate. For example, a null reference that is outside of your control.
2. An error that you, as a programmer, should have checked for in your code (such as ArrayIndexOutOf BoundsException where you should have paid attention to the size of the array).
Java exception handling designed to handle those pesky runtime errors will occur because of forces outside your code's control, and also essential for certain types of programming bugs that the compiler cannot detect.
Performing cleanup with finally
finally clause is executed whether or not an exception is thrown
The finally clause is necessary when you need to set something other than memory back to its original state. This is some kind of cleanup like an open file or network connection, something you've drawn on the screen, or even a swich in the outside world.
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
f();
sw.off();
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
sw.off();
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
sw.off();
}
}
The goal here is to make sure that the switch is off when main( ) is completed, so sw.off( ) is placed at the end of the try block and at the end of each exception handler. But it’s possible that an exception might be thrown that isn’t caught here, so sw.off( ) would be missed. However, with finally you can place the cleanup code from a try block in just one place
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
OnOffSwitch.f();
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
} finally {
sw.off();
}
}
Even in cases in which the exception is not caught in the current set of catch clauses, finally will be executed before the exception-handling mechanism continues its search for a handler at the next higher level
The finally statement will also be executed in situations in which break and continue statements are involved. Note that, along with the labeled break and labeled continue, finally eliminates the need for a goto statement in Java.
Using finally during return
(略)
Pitfall: the lost exception
There's a flaw in Java's exception implementation. It's possible for an exception to simply be lost. This happens with a particular configuration using a finally clause.
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.f(); // throw exception1
} finally {
lm.dispose(); //throw exception2
}
} catch(Exception e) { // 捕抓到exception2,丢失exception1
System.out.println(e);
}
}
you will typically wrap any method that throws an exception, such as dispose( ) in the example above, inside a try-catch clause
An even simpler way to lose an exception is just to return from inside a finally clause
Exception restrictions
When you override a method, you can throw only the exceptions that have been specified in the base-class version of the method. This is a useful restriction, since it means that code that work with the base class will automatically work with any object derived from the base class, including exceptions.
1. OK to add new exceptions for constructors
2. You can choose to not throw any exceptions, even if the base version does
3. Overridden methods can throw inherited exceptions
4. A derived-class constructor cannot catch exceptions thrown by its base-class constructor (原因:super(..)必须出现在第一行)
Although exception specifications are enforced by the compile during inheritance, the exception specifications are not part of the type of a method, which comprises only the method name and argument types.
the "exception specification interface" for a particular method may narrow during inheritance and overriding, but it may not widen—this is precisely the opposite of the rule for the class interface during inheritance.
Constructors
The constructor puts the object into a safe starting state, but it might perform some operation—such as opening a file that doesn’t get cleaned up until the user is finished with the object and calls a special cleanup method. If you throw an exception from inside a constructor, these cleanup behaviors might not occur properly. This means that you must be especially diligent while you write your constructor.
You might think that finally is the solution. But it’s not quite that simple, because finally performs the cleanup code every time. If a constructor fails partway through its execution, it might not have successfully created some part of the object that will be cleaned up in the finally clause.
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.out.println("Could not open " + fname);
// Wasn’t open, so don’t close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.out.println("in.close() unsuccessful");
}
throw e; // Rethrow
} finally {
// Don’t close it here!!!
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch(IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
}
One of the design issues with exceptions is whether to handle an exception completely at this level, to handle it partially and pass the same exception (or a different one) on, or whether to simply pass it on. Passing it on, when appropriate, can certainly simplify coding.
This is one of the downsides to Java: All cleanupother than memory cleanup—doesn’t happen automatically, so you must inform the client programmers that they are responsible.
The safest way to use a class which might throw an exception during construction and which requires cleanup is to use nested try blocks:
// If construction can fail you must guard each one:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
try {
// ...
} finally {
nc5.dispose();
}
} catch(ConstructionException e) { // nc5 constructor
System.out.println(e);
} finally {
nc4.dispose();
}
} catch(ConstructionException e) { // nc4 constructor
System.out.println(e);
}
Exception matching
When an exception is thrown, the exception-handling system looks through the "nearest" handlers in the order they are written. When it finds a match, the exception is considered handled, and no further searching occurs.
Matching an exception doesn’t require a perfect match between the exception and its handler. A derived-class object will match a handler for the base class.
Alternative approaches
The reason exception-handling systems were developed is because the approach of dealing with each possible error condition produced by each function call was too onerous, and programmers simply weren’t doing it. As a result, they were ignoring the errors. It’s worth observing that the issue of programmer convenience in handling errors was a prime motivation for exceptions in the first place.
One of the important guidelines in exception handling is "Don’t catch an exception unless you know what to do with it."
one of the important goals of exception handling is to move the error-handling code away from the point where the errors occur. This allows you to focus on what you want to accomplish in one section of your code, and how you’re going to deal with problems in a distinct separate section of your code. As a result, your mainline code is not cluttered with error-handling logic, and it’s much easier to understand and maintain.
Exception handling also tends to reduce the amount of error-handling code, by allowing one handler to deal with many error sites.
history
Perspectives
it’s worth noting that Java effectively invented the checked exception (clearly inspired by C++ exception specifications and the fact that C++ programmers typically don’t bother with them). However, it was an experiment which no subsequent language has chosen to duplicate.
Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result—decreased productivity and little or no increase in code quality.
Passing exceptions to the console
public static void main(String[] args) throws Exception {
// Open the file:
FileInputStream file =
new FileInputStream("MainException.java");
// Use the file ...
// Close the file:
file.close();
}
Converting checked to unchecked exceptions
Throwing an exception from main( ) is convenient when you’re writing simple programs for your own consumption, but is not generally useful.
The real problem is when you are writing an ordinary method body, and you call another method and realize, "I have no idea what to do with this exception here, but I don’t want to swallow it or print some banal message." With chained exceptions, a new and simple solution prevents itself. You simply "wrap" a checked exception inside a RuntimeException by passing it to the RuntimeException constructor.
try {
// ... to do something useful
} catch(IDontKnowWhatToDoWithThisCheckedException e) {
throw new RuntimeException(e);
}
This seems to be an ideal solution if you want to "turn off the checked exception—you don’t swallow it, and you don’t have to put it in your method’s exception specification, but because of exception chaining you don’t lose any information from the original exception.
This technique provides the option to ignore the exception and let it bubble up the call stack without being required to write try-catch clauses and/or exception specifications. However, you may still catch and handle the specific exception by using getCause( )
for(int i = 0; i < 4; i++)
try {
if(i < 3)
wce.throwRuntimeException(i);
else
throw new SomeOtherException();
} catch(SomeOtherException e) {
print("SomeOtherException: " + e);
} catch(RuntimeException re) {
try {
throw re.getCause();
} catch(FileNotFoundException e) {
print("FileNotFoundException: " + e);
} catch(IOException e) {
print("IOException: " + e);
} catch(Throwable e) {
print("Throwable: " + e);
}
}
}
Another solution is to create your own subclass of RuntimeException.
Exception guidelines
Use exceptions to:
1. Handle problems at the appropriate level. (Avoid catching exceptions unless you know what to do with them.)
2. Fix the problem and call the method that caused the exception again.
3. Patch things up and continue without retrying the method
4. Calculate some alternative result instead of what the method was supporsed to produce.
5. Do whatever you can in the current context and rethrow the same exception to a higher context
6. Do whatever you can in the current context and throw a different exception to a higher context
7. Terminate the program
8. Simplify. ( If your exception scheme makes things more complicated, then it is painful and annoying to use.
9. Make your library and program safer.(This is a short-term investment for debugging, and a long-term investment for application robustness.)
Summary
One of the advantages of exception handling is that it allows you to concentrate on the problem you’re trying to solve in one place, and then deal with the errors from that code in another place.
And although exceptions are generally explained as tools that allow you to report and recover from errors at run time, I have come to wonder how often the "recovery" aspect is implemented, or even possible. My perception is that it is less than 10 percent of the time, and even then it probably amounts to unwinding the stack to a known stable state rather than actually performing any kind of resumptive behavior.
Whether or not this is true, I have come to believe that the "reporting" function is where the essential value of exceptions lie.
by laying this question to rest—even if you do so by throwing a RuntimeException—your design and implementation efforts can be focused on more interesting and challenging issues
Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十二)之Error Handling with Exceptions的更多相关文章
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information
Runtime type information (RTTI) allow you to discover and use type information while a program is ru ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十)之Inner Classes
The inner class is a valuable feature because it allows you to group classes that logically belong t ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(八)之Reusing Classes
The trick is to use the classes without soiling the existing code. 1. composition--simply create obj ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(七)之Access Control
Access control ( or implementation hiding) is about "not getting it right the first time." ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(六)之Initialization & Cleanup
Two of these safety issues are initialization and cleanup. initialization -> bug cleanup -> ru ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十三)之Strings
Immutable Strings Objects of the String class are immutable. If you examine the JDK documentation fo ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(二)之Introduction to Objects
The genesis of the computer revolution was a machine. The genesis of out programming languages thus ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十一)之Holding Your Objects
To solve the general programming problem, you need to create any number of objects, anytime, anywher ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(九)之Interfaces
Interfaces and abstract classes provide more structured way to separate interface from implementatio ...
随机推荐
- ThinkPHP5.0 漏洞测试
ThinkPHP5.0 漏洞测试 自从ThinkPHP发布漏洞补丁以来,服务器不知道多少次受到了批量扫描漏洞来抓取肉鸡的请求 虽然官方早已发布补丁,还是想试一下TP漏洞,测试两个漏洞 一.全版本执行漏 ...
- hdu3665Floyd解法
题目链接:http://icpc.njust.edu.cn/Problem/Hdu/3665/ Floyd是经典的dp算法,将迭代过程分成n个阶段,经过n个阶段的迭代所有点对之间的最短路径都可以求出, ...
- JDBC(三)----JDBC控制事务
## JDBC控制事务 1.事务:一个包含多个步骤的业务操作.如果这个业务员操作被事务管理,则这多个步骤要么同时成功,要么同时失败. 2.操作: 1.开启事务 2.提交事务 3.回滚事务 3.使用C ...
- Android通知栏前台服务
一.前台服务的简单介绍 前台服务是那些被认为用户知道且在系统内存不足的时候不允许系统杀死的服务.前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下--这就意味着通知只有在这个 ...
- Python中保留两位小数的几种方法
https://blog.csdn.net/Jerry_1126/article/details/85009810 保留两位小数,并做四舍五入处理方法一: 使用字符串格式化>>> a ...
- 基于树莓派与YOLOv3模型的人体目标检测小车(一)
项目介绍: 本科毕业选的深度学习的毕设,一开始只是学习了一下YOLOv3模型, 按照作者的指示在官网上下载下来权重,配好环境跑出来Demo,后来想着只是跑模型会不会太单薄,于是想了能不能做出来个比较实 ...
- Building Applications with Force.com and VisualForce(Dev401)(十三):Implementing Business Processes:Automating Business Processes Part II
ev401-014:Implementing Business Processes:Automating Business Processes Part II Module Agenda1.Multi ...
- [LeetCode] 937. Reorder Data in Log Files 日志文件的重新排序
You have an array of `logs`. Each log is a space delimited string of words. For each log, the first ...
- js实现动态球球背景
document.getElementsByTagName("body")[0].style.backgroundColor="#000" //构造函数 fun ...
- 5分钟配置好你的AI开发环境
作者 | Revolver 无论是第一次设置TensorFlow的新手数据科学爱好者,还是使用TB级数据的经验丰富的AI工程师,安装库.软件包或者框架总是一个困难又繁琐的过程.但是像Docker这样的 ...