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 ...
随机推荐
- Python字符串及基本操作(入门必看)
基础入门的知识一直没有更新完,今天小张接着给大家带来入门级的字符串的常用操作.本文适合刚入门的小白,大佬们请绕过. 一.定义 字符串的意思就是“一串字符”,比如“Hello,Charlie”是一个字符 ...
- Leetcode_面试题62. 圆圈中最后剩下的数字(约瑟夫环)
经典的约瑟夫环,n个人排成一圈,第m个出队. 递归 code1 class Solution { public: int f(int n,int m){ if(n==1){ //递归边界,最后一个 r ...
- 手把手教你学Git
Git 使用手册独家实战 0.查看本机公钥 步骤: 1.进入.ssh目录 cd ~/.ssh 2.找到id_rsa.pub文件 ls / ll 3.查看文件 cat id_rsa.pub JackFe ...
- effective-java学习笔记---静态工厂方法替代构造方法
使用静态方法的优点: 1.它们是有名字的,生成的客户端代码更易阅读. 如:返回素数的静态方法 BigInteger.probablePrime 2.与构造方法不同,它们不需要每次调用时都创建一个对象. ...
- 给 EF Core 查询增加 With NoLock
给 EF Core 查询增加 With NoLock Intro EF Core 在 3.x 版本中增加了 Interceptor,使得我们可以在发生低级别数据库操作时作为 EF Core 正常运行的 ...
- Jmeter4.0接口测试之WebServices(四)
关于什么是web services,可以到W3C中查看详细的信息,本文章主要介绍使用Jmeter怎么来做web services的接口测试,首先它也是基于HTTP协议的,我们实现电话号码归属地的查询, ...
- Ali_Cloud++:安装 RabbitMQ安装及环境配置
注意事项:rabbitMA版本和erlang并不是同步更新的,会出现版本不匹配,安装不了. 两都版本对应 参考官网文档 其它下载地址 1):Erlang安装 (因为是erlant语言编写的, ...
- coding++:Spring_IOC(控制反转)详解
IoC是什么: 1):Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想. 2):在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的 ...
- Python python对象 deque
# deque对象 ''' class collections.deque([ iterable [,maxlen ] ] ) 返回一个从左到右(使用append())初始化的新deque对象,其中包 ...
- office2010安装与破解,笔者亲测可用!!!!!!
我们首先需要准备office2010安装包与破相应的破解软件.软件包的获取方式:扫码关注[猿成长],,回复 office2010安装,即可获取,下载解压后文件目录结构如下图所示: 打开安装程序文件夹, ...